第 6章 Java的线程
2012-3-7 Java面向对象程序设计教程 2
主要内容
? 6.1 线程的概念与 POSIX标准
? 6.1.1 线程的概念
? 6.1.2 线程的 POSIX标准
? 6.2 多线程的 Java实现
? 6.2.1 线程的创建方法
? 6.2.2 线程的状态
? 6.3 互斥线程间的同步机制
? 6.3.1 多线程带来的冲突问题
? 6.3.2 共享资源合理使用的实现
? 6.3.3 按同步协调程度划分的线程间的关系
6.1 线程的概念与 POSIX标准
2012-3-7 Java面向对象程序设计教程 4
线程的概念
? 线程其实是控制线程( Thread of
control)的简写。
? 控制线程就是程序运行时的路径,是在一
个程序中与其它控制线程无关的能够独立
运行的代码片段。
? 与线程有关的几个名词,
? 进程与线程
? 并发性与并行性
? 异步与同步
2012-3-7 Java面向对象程序设计教程 5
基于进程的多任务处理环境
? 进程是重量级的内核级实体,需要分配它们自己独立的地
址空间:包括有虚拟内存映射、文件描述符、用户 ID等,
并且每个进程都有属于自己的这些集合。用户的程序要访
问进程结构中的数据、查询或改变状态,唯一方法是通过
系统调用。进程间通信和转换是昂贵和受限的。
2012-3-7 Java面向对象程序设计教程 6
基于线程的多任务处理环境
? 线程是轻量级实体,由寄存器、堆栈和某些数据组成。进程
结构的其余部分由所有的线程所共享:如地址空间、文件描
述符等 —— 它们共享相同的地址空间并且共同分享同一个进
程。线程间通信是便宜的,线程间的转换也是低成本的。
2012-3-7 Java面向对象程序设计教程 7
线程并发
? 并发性( Concurrency)是两个或多个线程(或传统的进
程)可以同时在执行代码之中;可以是相同的代码,也可
以是不同的代码。这些线程可以一次执行,也可以多次执
行,即一个已开始执行但被中断,而另外一个已开始了。
? 但在给定的时间点上,只有一个在 CPU在处理一个线程 。
2012-3-7 Java面向对象程序设计教程 8
线程并行
? 并行性( Parallelism)是针对多处理器环境而言的,
是指两个或多个线程真正同时运行在不同的 CPU上。
? 在多处理器机上,很多不同的线程可以并行运行,或者
说是同时运行。
2012-3-7 Java面向对象程序设计教程 9
异步与同步
? 异步世界需要处理同步业务
? 有 3个人在 3个收银台前结帐
? 3个人结帐是异步的
? 每个人信用卡在银行业务处理时需保证同步
2012-3-7 Java面向对象程序设计教程 10
线程的 POSIX标准
6.2 多线程的 Java实现
2012-3-7 Java面向对象程序设计教程 12
主线程
? 通过调用 Thread类的 currentThread方法
来查看主线程,
? Thread.currentThread()
? 举例,MainThread.java
2012-3-7 Java面向对象程序设计教程 13
创建线程有两种方式
? 通过直接扩展 Thread类来直接创建线程,
? public class BidThread extends Thread
? new BidThread(10,1000).start()
? 举例,BidThread.java, Auction.java
? 通过设计一个类,使之实现 java,lang,Runnable接口,再把该接
口作为参数传递给 Thread类的构造方法后间接创建线程,
? public class BidRun implements Runnable
? new Thread(new BidRun(10,1000)).start()
? 举例,BidRun.java, Auction.java
2012-3-7 Java面向对象程序设计教程 14
采用间接创建线程的原因
? 第一个理由是我们并不改变线程本身的性
质,仅覆盖 run方法,并没有增加新的功能,
因此将 Thread扩展子类并不恰当,这不太
符合类扩展规范。
? 第二个理由是:如果实现 Runnable接口,
它可能使我们所设计的类扩展其它类型而
变得更为有用。
2012-3-7 Java面向对象程序设计教程 15
线程内容的定义
? 覆盖或实现 run方法 …
? start方法一般只用来触发线程,把线程的
内容放在这个方法体中不太规范,而且这样
做实际上线程不会竞争运行。
? 思考与练习 11
? 如果直接调用 run方法,线程也不会竞争运
行,必须通过 start方法间接调用 run方法。
? 思考与练习 12
2012-3-7 Java面向对象程序设计教程 16
Thread类一些方法使用的例子
? 线程优先级,PriorityTest.java
? 创建守护线程,DaemonTest.java
? 线程列表,ThreadList.java
? 线程组信息,ThreadGroupTest.java
2012-3-7 Java面向对象程序设计教程 17
线程的状态
2012-3-7 Java面向对象程序设计教程 18
与线程所处状态有关的方法举例
? join方法的例子,JoinTest.java
? yield方法的例子,YieldTest.java
? interrupt方法的例子,InterruptTest.java
6.3 互斥线程间的同步机制
2012-3-7 Java面向对象程序设计教程 20
多线程带来的冲突问题
? 实例变量也是共享的
? 举例,ThreadSharedData.java
? 使用 volatile关键字通知线程及时取得共享
变量的更新值
? 共享资源使用冲突
2012-3-7 Java面向对象程序设计教程 21
银行业务中共享资源冲突示意
2012-3-7 Java面向对象程序设计教程 22
共享资源合理使用的实现
? 使用同步方法
? 调用被 synchronized关键字修饰的方法
? 使用同步代码块
? synchronized(obj) {
? // statements to be synchronized
? }
? 举例,AccountSimulator.java
2012-3-7 Java面向对象程序设计教程 23
讨论:同步代码块中对象锁的选择
? 类本身,
synchronized(TransferManager.class)
? 静态对象,
static Object obj=new Object();
synchronized(obj)
? 实例对象,
synchronized(this)
? 举例:见 AccountSimulator.java
2012-3-7 Java面向对象程序设计教程 24
思考
? 在 AccountSimulator.java例子中为什么
synchronized(this) 无法上锁以保证同步?
TransferManager tm1=new TransferManager(checking,savings);
TransferManager tm2=new TransferManager(savings,checking);
Thread t1=new Thread(tm1);
Thread t2=new Thread(tm2);
t1.start();
t2.start();
在线程中使用不同的实例对象,若用
this,实际上两个线程分别使用一个对
象锁。
2012-3-7 Java面向对象程序设计教程 25
思考
? 如果要求使用 synchronized(this) 也能保证同
步,程序应如何改动?
TransferManager tm1=new TransferManager(checking,savings);
TransferManager tm2=new TransferManager(savings,checking);
Thread t1=new Thread(tm1);
Thread t2=new Thread(tm1);
t1.start();
t2.start();
让线程使用相同的实例对象,则用 this
时,两个线程使用一个对象锁。
2012-3-7 Java面向对象程序设计教程 26
讨论:同步导致的死锁问题
? 死锁发生在当两个线程对两个同步对象有循环依
赖关系时。
? 例如,假定一个线程进入了对象 X的对象锁而另
一个线程进入了对象 Y的对象锁。如果 X的线程试
图调用 Y的同步方法,它将像预料的一样被锁定。
而 Y的线程同样希望调用 X的一些同步方法,线程
永远等待,因为为到达 X,必须释放自己的 Y的锁
定以使第一个线程可以完成。
? 举例,DeadLock.java
2012-3-7 Java面向对象程序设计教程 27
按同步协调程度划分的线程间的关系
? 不相关的线程
? 相关但不需要同步的线程
? 互斥线程
? 交互式互斥线程
2012-3-7 Java面向对象程序设计教程 28
不相关的线程,Drinker.java
2012-3-7 Java面向对象程序设计教程 29
相关但无需同步线程,Kid.java
2012-3-7 Java面向对象程序设计教程 30
互斥线程,Customer.java
2012-3-7 Java面向对象程序设计教程 31
交互式的互斥线程,Consumer.java
2012-3-7 Java面向对象程序设计教程 32
交互式互斥线程使用的 wait/notify机制
? wait方法意味着,即使线程拥有锁,由于得不到期望的数
据,因无法做进一步的处理,也只能放弃锁,使线程处于
等待状态,而让另一个线程继续工作。
? notify方法可以通知位于等待队列中的任何一个线程,但
不一定按照 FIFO的顺序进行而是遵循 JVM的调度。
? notify与 notifyAll方法之间的差别是,
? notifyAll方法将唤醒等待当前对象的所有线程,对于所有读取同
一信息或者等待同一答案的消费者等待队列而言,这种方法是恰
当的。
? 调用 wait/notify的方法必须声明为 synchronized,否则
将引发运行时异常
java.lang.IllegalMonitorStateException。