Lesson 12,Threads
多线程程序设计
LiFan(李凡)
Chengdu University of Information Technology
The Department Of Computer Science
2005
What is the thread?
? Java语言的一个重要特性是支持多线程的程
序设计,多线程是实现 并发 的一种有效手段
? 线程 是一个能独立执行自身指令的控制流
? 一个程序中可以同时运行多个相对独立的线
程,这样的程序称为 多线程程序
? 操作系统将 CPU的执行划分为非常小的时间
片,根据一定的规则在不同的线程之间切换,
使每个线程都得到执行的机会
Progresses and Threads
? 进程与线程 是两个不同的概念
? 进程 是一个程序的执行序列,由操作系统调
度,是资源分配的基本单位; 线程 是一个进
程中的一个子序列,一般由程序负责管理,
是执行的基本单位
? 线程共用相同的地址空间,线程之间的通信
是非常方便的,它们共同构成一个进程;进
程之间却有不同的地址空间
Creating Threads
? 类 java.lang.Thread 的实例表示线程对象
? 接口 java.lang.Runnable 的实现类实例表示
一个可作为线程运行的对象
? 创建一个线程可以采用两种方式,
(1) 创建 Thread类的子类
(2) 实现接口 java.lang.Runnable
Class java.lang.Thread
? Thread类有以下形式的构造函数,
Thread()
Thread(String name)
Thread(Runnable target)
Thread(Runnable target,String name)
? Thread类中线程的主要功能方法是,
public void run() {}
? Thread类的子类需覆盖 run()方法
class MyThread extends Thread{
public void run(){
System.out.println(“executing as a thread”);
}
}
Thread th=new MyThread();
Interface java.lang.Runnable
? Runnable接口中声明了唯一的方法
public interface Runnable{
public void run();
}
? Runnable接口的实现类负责 run()方法的实现,
提供作为线程运行时的功能
? Runnable接口的实现类对象必须转换为 Thread
类的对象才能以线程的方式被运行
class MyRunner implements Runnable{
public void run(){
System.out.println(“executing as a thread”);
}
}
MyRunner runner=new MyRunner();
Thread th=new Thread(runner);
run() and start() Methods
? Thread类和 Runnable接口的 run()方法 是一
个回调 (Callback)方法,当 Thread类实例表
示的线程被执行时,此方法将被 JVM调用,
完成线程的功能
? Thread类的 start()方法 用于启动一个线程,
使线程进入就绪状态,等待线程调度器的执
行 Thread th=new MyThread();
th.start(); 启动线程
Corrupting Data Problem
? 当多个线程交替访问并修改同一个数据对象
时,可能会破坏数据的一致性,或称为 污染
(corrupt)数据
? 共多个线程同时访问并修改的数据对象也称
为 临界区域 (critical regions)
T1 T2 X
100
300
0
X=a;
a=X; b=X;
a=a+200; b=b-100;
X=b;
Synchronization
? 通过 同步 (synchronizing)对临界区域的访问
可以解决数据污染问题
? 同步锁协议,线程在操作某个对象前,必须
获得该对象上的锁 (lock),完成操作后释放
对象上的锁,没有得到锁的线程必须等待锁
的释放,如果线程在操作对象的过程中发生
了异常,则自动释放对象上的锁
T1 T2 X
100
300
200
X=a;
a=X;
a=a+200;
获得 X上的锁
释放 X上的锁
释放 X上的锁
获得 X上的锁
b=X;
b=b-100;
X=b;
b=X;
等待 X上的锁
Synchronization
? 在 Java语言中,通过 被关键字 synchronized修
饰的方法 或 synchronized语句块 实现对代码
的同步
? 包含在 synchronized方法或语句块中的代码
称为 被同步的代码 (Synchronized Code)
? 当线程访问被同步的代码时,必须首先竞争
代码所属的类的对象上的锁,否则线程将等
待 (阻塞 ),直到锁被释放
Synchronized Methods
? 当关键字 synchronized修饰非静态方法时,线
程访问方法获取的锁是方法所属的 类的对象
上的锁,即引用 this
? 当关键字 synchronized修饰静态方法时,线程
访问方法获取的锁是方法所属的 类的 Class对
象上的锁,即类的静态域 类名,class
? 线程在被同步的方法中调用同一对象或类上
的其它被同步的方法无需再获取锁
public class BankAccount{
private long number;
private long balance;
public BankAccount(long initialDeposit){
balance=initialDeposit;
}
public synchronized long getBalance(){
return balance;
}
public synchronized void deposit(long amount){
balance+=amount;
}
……
}
Synchronized Statements
? 同步语句 (synchronized statements)的一般形
式如下,
synchronized(<锁对象引用 >){
… 被同步的代码 …
}
? 同步语句 提供了比 被同步的方法 更细粒度
(finer granularity)的锁机制,增强了类的并
发性,提高了运行时的性能
class SeparateGroups{
private double aVal=0.0;
private double bVal=1.1;
protected Object lockA=new Object();
protected Object lockB=new Object();
public double getA(){
synchronized(lockA){
return aVal;
}
}
public double getB(){
synchronized(lockB){
return bVal;
}
}
}
锁对象
同步锁对象 lockA
同步锁对象 lockB
Synchronization Designs
? 在程序中引入同步机制是一个有挑战性的工作,
必须精心的设计,一般可使用两种设计思路,
(1) 客户端同步
让客户端代码使用同步语句获取将操作的对
象上的锁,再在语句块中操作对象
(2) 服务端同步
设计类时将对临界区域的操作封装在被同步
的方法或方法中的同步语句块中,客户端代码无
需再进行同步控制
Communication between threads
? Object类提供了用于线程间通信的方法,
public final void wait(long timeout)
public final void wait(long timeout,int nanos)
public final void wait()
public final void notifyAll()
public final void notify()
wait() Method
? 当测试条件不成立时,调用锁对象的 wait()或
wait(…) 方法将使当前线程进入阻塞状态,直
到以下事件发生,
(1) 锁对象的 notify()方法被调用
(2) 锁对象的 notifyAll()方法被调用
(3) 线程对象的 interrupt()方法被调用
(4) 超时
? wait()方法等价于 wait(0),表示没有超时时限
synchronized void doWhenCondition(){
while(!<测试条件 >)
wait();
… 当测试条件成立时执行的代码 …
}
Object lockObj=new Object();
void doWhenCo dition(){
synchronized(lockObj){
while(!<测试条件 >)
lockObj.wait();
… 当测试条件成立时执行的代码 …
}
}
条件测试必须
放在循环中 !
notify(),notifyAll() Methods
? notify()方法 将通知因在锁对象上调用 wait()
方法而被阻塞的任意一个线程,使它有机会
检查测试条件的改变
? notifyAll()方法 将通知因在锁对象上调用
wait()方法而被阻塞的所有线程,使它们都
有机会检查测试条件的改变
? notify()方法虽然没有 notifyAll()方法的性能
高,但是具有更高的可靠性
Object lockObj=new Object();
void changeCondition(){
synchronized(lockObj){
<改变测试条件 >
lockObj.notifyAll();
}
}
synchronized void changeCondition(){
<改变测试条件 >
notifyAll();
}
class Queue{
private Cell head,tail;
public synchronized void add(Object o){
Cell p=new Cell(o);
if(tail==null)
head=p;
else
tail.next=p;
p.next=tail;
tail=p;
notifyAll();
}
public synchronized Object take() throws InterruptedException{
while(head==null)
wait();
Cell p=head;
head=head.next;
if(head==null) tail=null;
return p.item;
}
}
Thread Priorities
? 每个线程都具有一个执行时的优先级别
? Thread类提供了与优先级相关的常量和方法,
static final int MIN_PRIORITY
static final int NORM_PRIORITY
static final int MAX_PRIORITY
int getPriority()
void setPriority(int newPriority)
? 一般使执行时间长的线程具有较低的优先级,使
执行时间短的线程具有较高的优先级
Thread Scheduling
? Java的 线程调度机制,低优先级的线程必须
被阻塞直到高优先级的线程执行结束或被阻
塞时才能得到执行机会
? Thread类提供了三个静态方法实现对线程的
主动调度,
public static void sleep(long millis)
public static void sleep(long millis,int nanos)
public static void yield()
Deadlocks
? 当两个线程以不同的顺序获取一组对象上的
锁,并且在线程的任务结束前不会释放已获
取的锁时,可能导致线程相互等待对方已获
取的锁,造成 死锁
T2
获得 Y上的锁
释放 Y上的锁
获得 X上的锁
执行任务
释放 X上的锁
T1
获得 X上的锁
释放 X上的锁
获得 Y上的锁
执行任务
释放 Y上的锁
T1
获得 X上的锁
等待 Y上的锁
T2
获得 Y上的锁
等待 X上的锁
Ending Thread Execution
? 如果线程只执行一次性的任务,则当 run()方
法执行完毕,线程也停止运行,进入终止状
态,等待垃圾收集器回收,这也是终止线程
的 最安全的方式
? 不要使用线程对象上的 stop()方法或 destroy()
方法来停止线程!
Waiting for a Thread to Complete
? 当线程调用另一个线程对象的 join方法时,
如果被调用线程还在运行,则当前线程被阻
塞,直到被调用线程停止运行,当前线程才
能结束运行
? Thread类的非静态 join方法有以下形式,
public final void join(long millis)
public final void join(long millis,int nanos)
public final void join()
class CalcThread extends Thread{
private double result;
public void run(){
result=doCalculate();
}
public double getResult(){
return result;
}
public void doCalculate(){
… 执行耗时的计算工作 …
}
}
class ShowJoin{
public static void main(String[] args){
CalcThread calc=new CalcThread();
calc.start();
doSomethingElse();
try{
calc.join();
System.out.println(“Result:,+calc.getResult());
}catch(InterruptedException e){
System.out.println(“No answer,interrupted!”);
}
}
}
Interrupting a thread
? 可以使用线程对象的 interrupt()方法 中断该线程的
执行,该方法调用的结果可以分为以下情况,
(1) 当线程因调用了 wait方法,sleep方法或 join方法
被阻塞时,线程将被唤醒,并在 run方法中接收到
InterruptedException异常
(2) 当线程因执行 I/O操作而被阻塞时,线程将被唤
醒,该线程打开的 I/O资源将被自动关闭,并在 run
方法中接收 ClosedByInterruptedException异常
(3) 当线程没被阻塞时,线程的中断标志位将被设
置,可调用 isInterrupted()方法判断
User Thread and Daemon Thread
? 线程可分为两类,用户线程 和 后台线程
? 每个应用程序启动时 JVM会创建该应用程序的主
线程,用于执行 main方法,可在 main方法中调用
Thread.currentThread()方法 主线程对象
? 用户线程和后台线程的主要区别 是用户线程能保
持程序的执行,而后台线程不能保持程序的执行,
当最后一个用户线程结束后,所有的后台线程都
会被强制终止
? 通过 setDaemon(true)方法 可将线程变为后台线程
Volatile Fields
? 当使用 基本数据类型的静态域 存放共多个线
程访问和修改的共享数据时,Java语言保证
对域变量的读写操作是原子操作
? 如果不使用同步操作,JVM不能保证线程读
取该静态域的值是该静态域最近的修改值
? 使用 关键字 volatile修饰该静态域将保证线程
读取该静态域的最近修改值
ThreadLocal Variables
? ThreadLocal类 内部维护了一张线程实例与
相关数据对象 (Object引用 )的映射表,通过
set方法 和 get方法 可以存取当前线程控制的
数据
? ThreadLocal类的变量通常被声明为静态的,
并且在包范围内可见,这些变量可方便地被
某个线程的一组方法访问
class FileSavingService{
static ThreadLocal ouput=new ThreadLocal();
public void service(){
try{
final OutputStream s=new FileOutputStream(“…”);
Runnable r=new Runnable(){
public void run(){
output.set(s);
try{ doService(); }
catch(IOException e) { … }
finally{
try{ s.close() }
catch(IOException e) {}
}
}
}
new Thread(r).start();
}
void doService() throws IOException{
((Output)output.get()).write(…);
}
}
Assignments (作业 )
? 编写课本 245页例程 Queue中使用的表
示队列节点的类 Cell,并使用多个线程
操作 Queue对象,观察执行效果
? 课本 231练习 10.1
? 课本 248练习 10.6
(将程序源码文件和编译后的字节码文件打包上传 )