第十章 多线程
杨 娟
it@126.com
Java 程序设计
教学内容
? 线程的概念
? 多线程的实现
? 线程的生命周期
? 线程的中断
? 线程的优先级
? 同步线程
? 小结
10.1 线程的概念
什么是多线程
线程的概念模型
多线程实例
? 食堂打饭
? 银行问题
? 公交问题
? 多媒体网页
? 网络聊天 (QQ)
多线程特点
? 并行性(同时)
? 实时性(及时)
进程与线程的区别
? 进程面向不同的软件
比如:同时运行的 word 和 PowerPoint
进程间没有公共数据(内存)
? 线程面向一个软件内的不同事务
比如:网络聊天服务器软件为每一个用户采用一
个线程及时接收和转发该用户信息
线程涉及公共数据(内存)
线程的概念模型
? Java内在支持多线程,它的所有类都是在多线程
下定义的,Java利用多线程使整个系统成为异步
系统。 Java中的线程由三部分组成,如图 10.1所
示。
图 10.1线程
1,虚拟的 CPU,封装在
java.lang,Thread类中。
2,CPU所执行的代码,传递
给 Thread类。
3,CPU所处理的数据,传递
给 Thread类。
10.2多线程的实现
多线程的实现方法
? 通过创建 Thread类的子类来实现;
? 通过实现 Runable接口的类来实现。
通过 Thread类实现多线程
1,设计 Thread的子类
2,根据工作需要重新设计线程的 run方法
3,使用 start方法启动线程,将执行权转交到
run。
? Java中有一个线程类 Thread,该类中提供
了 run是一个空方法。为此,我们可以继承
Thread,然后覆盖 (override)其中的 run,
使得该线程能够完成特定的工作。见
mod10\MultiThreadExample.java。
例 10.1
public class MultiThreadExample1{
public static void main(String []args){
new MyThread("A").start(); //启动线程 A
new MyThread("B").start(); //启动线程 B
}
}
class MyThread extends Thread{
public MyThread(String n){super(n); }
//n:线程名称
public void run(){
for(int i=0;i<5;i++){
// 睡眠一段随机时间
Thread.sleep((long)(Math.random() * 1000));
//显示线程名称
System.out.print(getName());
}
}
}
实现 Runnable接口
? Mod10\MultiThreadExample2
? Runnable接口中定义了唯一的方法,public void run();
? 任何实现了 Runnable接口的类所生成的对象均可用于创
建线程对象 。 例如类 CustomThread实现了 Runnable接口,
因此可以这样来创建一个线程对象:
Runnable a=new CustomThread("A");
Thread t=new Thread(a);
或是更简洁一点:
Thread t=new Thread(new CustomThread("A"));
启动这样创建的线程, 同样使用 start方法:
t.start();//启动线程 A
从理论上讲,定制线程类可以使用上述两种方法中的
任意一种。但是由于 Java只支持单继承,因此,当你
定制的线程类需要继承其他类时,就只能使用实现
Runnable接口的方法。
例 10.2
public class MultiThreadExample2{
public static void main(String []args){
Thread t1=new Thread(new
CustomThread("A"));
Thread t2=new Thread(new
CustomThread("B"));
t1.start(); //启动线程 A
t2.start(); //启动线程 B
}
}
class CustomThread implements Runnable{
String name;
public CustomThread(String n){
name=n;
}
public void run(){
Thread current=Thread.currentThread();
//取得当前线程
for(int i=0;i<5;i++){
// 睡眠一段随机时间
current.sleep((long)(Math.random() * 1000));
System.out.print(name); //打印线程名称
}
}
}
10.3线程的生命周期
10.3线程的生命周期
新建 就绪
阻塞 运行 死亡
10.3.1创建线程
? 创建一个线程对象 t1。
Thread t1=new Thread(new CustomThread ("A"));
? 注意, 该语句执行完毕后, 线程对象 t1处于 New状态, 它
并没有拥有运行线程所需要的系统资源, 也就是说这个时
候线程还不可运行 。 当线程处于这 New状态的时候, 唯一
能做的就是启动 (start)这个线程 。 调用 start之外的任何方
法都不能使该线程执行, 并且会引发一个
IllegalThreadStateException异常 。 事实上, 在线程的生命
周期中, 任意时刻调用一个当前不能被执行的方法, 线程
都将抛出一个 IllegalThreadStateException异常 。
10.3.2 启动线程
? 线程对象创建后, 紧接着执行的语句为:
t1.start();//启动线程 A
start方法创建了运行线程所必需的系统资源, 并调用线程
的 run方法 。 start方法返回之后, 线程就进入可运行
(Runnable)状态 。
10.3.3 线程运行
? 处于运行状态 (Running)的线程也可能被调
度到可运行状态 (Runnable)-例如所分配的
CPU时间片结束或是调用 yield方法主动让
出 CPU-从而给其他处于可运行状态的线
程以执行机会 。
10.3.4 线程阻塞
? 当以下任一事件发生时, 正在运行的线程将由运
行状态转化为阻塞 (Blocked)状态:
(1)休眠方法被调用
(2)线程调用 wait方法,并且等待一个指定的条件
被满足
(3)线程在 I/O处阻塞
? 当线程处于阻塞状态时, 即使处理器处于空闲状
态, 该线程也不会被执行 。 此外, 当线程 t1睡眠
结束, 也并不立刻进入运行状态, 而是先进入可
运行状态, 等候调度 。
10.3.5 终止线程
? 线程的 run方法退出后, 自然进入死亡 (Dead)状态 。
? ! 虽然也可以调用线程的 stop方法来强制杀死一个线程,
但是不推荐使用 。
? 此外, 如果想知道线程是否处于活动状态, 可以用 isAlive
方法来判断 。 isAlive在线程已经启动 (start)并且没有死亡
(Dead)时返回值是 true。 假如该方法返回的是 false,那么
可以知道这个是新线程 (已创建, 还没有进入可运行状态 )
或者当前线程已经死亡 。
10.4 线程中断
? 我们已经知道, 当线程的 run方法运行结束, 该线程也就
自然终止 。 虽然也可以调用线程的 stop方法来强制杀死一
个线程, 但该方法是不安全的, 因此不推荐使用 。 Java中
还提供了 interrupt方法用来中断一个线程 。 当调用一个
线程的 interrupt方法时, 即向该线程发送一个中断请求,
并将该线程的, interrupted”状态值置为 true。
? 除了 interrupted方法外, 还有一个 isInterrupted方法 。
这两个方法均用于判断当前线程是否已经被中断请求过,
不同之处在于,interrupted方法在返回, interrupted”
状 态 值 的 同 时 会 将 其 值 重 新 设 置 为 false, 而
isInterrupted方法只是简单返回, interrupted”的状态
值 。
10.5 线程的优先级
? 当线程被创建时, 其优先级是由创建它的线程的
优先级所决定的 。
? 可 以 在 线 程 创 建 之 后 的 任 意 时 刻 通 过 调 用
setPriority方法来修改线程的优先级 。
? 线程的优先级是在 MIN_PRIORITY( 值为 1) 和
MAX_PRIORITY(值为 10)范围内的一个整数值 。 数
值越大, 代表线程的优先级越高 。
? 见 mod10\PriorityExample.java
例 10.4
public class PriorityExample{
public static void main(String []args){
Thread a=new PThread("A");
Thread b=new PThread("B");
a.setPriority(7);
b.setPriority(1);
a.start();
b.start();
}
}
class PThread extends Thread{
public PThread(String n){
super(n); //线程名称
}
public void run(){
for(int i=0;i<5000000;i++){
if(i%500000==0) //输出线程名称
System.out.print(getName());
}}}
10.5.2利己线程
? 在线程中可以通过调用 sleep方法, 放弃当前线程对处理器的使用,
从而使得各个线程均有机会得到执行 。 但是有的时候, 也有线程可能
不遵循这个规则 。 for循环是一个紧密循环 (tight loop)。 一旦运行
系统选择了有这种循环体的线程执行, 该线程就不会主动放弃对处理
器的使用权, 除非 for循环自然终止或者该线程被一个有更高优先级
的线程抢占 。 这样的线程, 我们称为利己线程 。
? 有些情况下, 利己线程并不会引起问题, 但是在某些情况下, 利己线
程长久占有处理器的使用权, 就会让其他的线程在得到处理器的使用
权之前等待一个很长的时间 。
10.5.3分时方式
? 为了解决在利己线程可能长时间占据 CPU
的问题,有些系统通过分时方式 (time-
slicing)来限制利己线程的执行 。
? 在分时方式中,处理器的分配按照时间片来
划分,对于那些有相同最高优先级的多个线
程,分时技术会交替地分配 CPU时间片给
它们执行。当分配给一个线程 (即使该线程
是利己线程 )的时间片结束时,即使该线程
没有运行结束,也会让出 CPU的使用权。
10.6 线程同步
? 线程必须关注与其并发执行的其他线程的状
态和行为 。 一个很典型的例子就是生产者 /
消费者问题 。
? 在线程同步我们主要讲:
?对象锁
?可重入锁
?notifyAll和 wait方法
对象锁
? 为了使得线程 p和线程 c不能同时对 box对象进行操作,我
们在 put和 get方法前加上 synchronized(同步 )关键字 。
? Java运行系统为每个对象分配了唯一的对象锁 (object
lock),当一个线程调用对象中被同步的方法来访问对象
时,这个对象就会被锁定。在该线程退出同步方法 (交出
对象锁 )之前,其他线程不能再调用该对象中任何被同步
的方法。
? !对象锁的取得和释放由 Java运行系统自动完成,但始终
遵循以下规则:在任何时刻,一个对象的对象锁至多只能
被一个线程拥有。
可重入锁
? Java中的对象锁是可重入的,即 Java运行系统允许一个线
程再次取得已为自己控制对象锁。锁的可重入性非常重要,
这可以防止一个线程的死锁。当一个线程 (不妨称为 t)调
用 Reentrant类型对象 (不妨称为 r)中的方法 a时,对象 r被
锁定。此时 a又调用 b,由于 b也是一个同步的方法,因此
线程 t需要再次取得对象 r的对象锁,才能执行方法 b。由
于 Java运行系统支持锁的重入,线程 t可以再次取得对象 r
的对象锁,从而方法 b能够得到执行。
? !如果不支持锁的重入,调用同步方法就有可能引起死锁 。
notifyAll和 wait方法
? 方法 wait使得当前线程进入阻塞状态,同时交出
对象锁,从而其他线程就可以取得对象锁。
? 方法 notifyAll唤醒的是由于等待 notifyAll方法
所在对象 (这里是 box)而进入等待状态的所有线程,
被唤醒的线程会去竞争对象锁,当其中某个线程
得到锁之后,其他的线程重新进入阻塞状态。
Java实例 —— (续)
本例中包括:
?Box.java
?Producer.java
?Consumer.java
?ProducerConsumerTest.java
Box.java
public class Box{
private int value;
private boolean available=false;
public synchronized int get() {
while (available == false) {
try {
wait(); //等待生产者写入数据
} catch (InterruptedException e) {
e.printStackTrace(); }
}
available = false;
// 通知生产者数据已经被取走,可以再次写入数据
notifyAll();
return value;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait(); // 等待消费者取走数据
} catch (InterruptedException e) {
e.printStackTrace(); }
}
this.value = value;
available = true;
//通知消费者可以来取数据
notifyAll();
}
}
Consumer.java
public class Consumer extends Thread {
private Box box;
private String name;
public Consumer(Box b,String n) {
box=b;
name=n;
}
public void run() {
int value = 0;
for (int i = 1; i < 6; i++) {
value = box.get();
System.out.println("Consumer " +name + " consumed," + value);
try { sleep((int)(Math.random() * 100));
} catch (InterruptedException e) {e.printStackTrace(); }
} }}
ProducerConsumerTest.java
public class ProducerConsumerTest {
public static void main(String[] args) {
Box box = new Box();
Producer p= new Producer(box,"p");
Consumer c= new Consumer(box,"c");
p.start();
c.start();
}
}
习题
10.1
10.2
10.3
谢谢
The End
杨 娟
it@126.com
Java 程序设计
教学内容
? 线程的概念
? 多线程的实现
? 线程的生命周期
? 线程的中断
? 线程的优先级
? 同步线程
? 小结
10.1 线程的概念
什么是多线程
线程的概念模型
多线程实例
? 食堂打饭
? 银行问题
? 公交问题
? 多媒体网页
? 网络聊天 (QQ)
多线程特点
? 并行性(同时)
? 实时性(及时)
进程与线程的区别
? 进程面向不同的软件
比如:同时运行的 word 和 PowerPoint
进程间没有公共数据(内存)
? 线程面向一个软件内的不同事务
比如:网络聊天服务器软件为每一个用户采用一
个线程及时接收和转发该用户信息
线程涉及公共数据(内存)
线程的概念模型
? Java内在支持多线程,它的所有类都是在多线程
下定义的,Java利用多线程使整个系统成为异步
系统。 Java中的线程由三部分组成,如图 10.1所
示。
图 10.1线程
1,虚拟的 CPU,封装在
java.lang,Thread类中。
2,CPU所执行的代码,传递
给 Thread类。
3,CPU所处理的数据,传递
给 Thread类。
10.2多线程的实现
多线程的实现方法
? 通过创建 Thread类的子类来实现;
? 通过实现 Runable接口的类来实现。
通过 Thread类实现多线程
1,设计 Thread的子类
2,根据工作需要重新设计线程的 run方法
3,使用 start方法启动线程,将执行权转交到
run。
? Java中有一个线程类 Thread,该类中提供
了 run是一个空方法。为此,我们可以继承
Thread,然后覆盖 (override)其中的 run,
使得该线程能够完成特定的工作。见
mod10\MultiThreadExample.java。
例 10.1
public class MultiThreadExample1{
public static void main(String []args){
new MyThread("A").start(); //启动线程 A
new MyThread("B").start(); //启动线程 B
}
}
class MyThread extends Thread{
public MyThread(String n){super(n); }
//n:线程名称
public void run(){
for(int i=0;i<5;i++){
// 睡眠一段随机时间
Thread.sleep((long)(Math.random() * 1000));
//显示线程名称
System.out.print(getName());
}
}
}
实现 Runnable接口
? Mod10\MultiThreadExample2
? Runnable接口中定义了唯一的方法,public void run();
? 任何实现了 Runnable接口的类所生成的对象均可用于创
建线程对象 。 例如类 CustomThread实现了 Runnable接口,
因此可以这样来创建一个线程对象:
Runnable a=new CustomThread("A");
Thread t=new Thread(a);
或是更简洁一点:
Thread t=new Thread(new CustomThread("A"));
启动这样创建的线程, 同样使用 start方法:
t.start();//启动线程 A
从理论上讲,定制线程类可以使用上述两种方法中的
任意一种。但是由于 Java只支持单继承,因此,当你
定制的线程类需要继承其他类时,就只能使用实现
Runnable接口的方法。
例 10.2
public class MultiThreadExample2{
public static void main(String []args){
Thread t1=new Thread(new
CustomThread("A"));
Thread t2=new Thread(new
CustomThread("B"));
t1.start(); //启动线程 A
t2.start(); //启动线程 B
}
}
class CustomThread implements Runnable{
String name;
public CustomThread(String n){
name=n;
}
public void run(){
Thread current=Thread.currentThread();
//取得当前线程
for(int i=0;i<5;i++){
// 睡眠一段随机时间
current.sleep((long)(Math.random() * 1000));
System.out.print(name); //打印线程名称
}
}
}
10.3线程的生命周期
10.3线程的生命周期
新建 就绪
阻塞 运行 死亡
10.3.1创建线程
? 创建一个线程对象 t1。
Thread t1=new Thread(new CustomThread ("A"));
? 注意, 该语句执行完毕后, 线程对象 t1处于 New状态, 它
并没有拥有运行线程所需要的系统资源, 也就是说这个时
候线程还不可运行 。 当线程处于这 New状态的时候, 唯一
能做的就是启动 (start)这个线程 。 调用 start之外的任何方
法都不能使该线程执行, 并且会引发一个
IllegalThreadStateException异常 。 事实上, 在线程的生命
周期中, 任意时刻调用一个当前不能被执行的方法, 线程
都将抛出一个 IllegalThreadStateException异常 。
10.3.2 启动线程
? 线程对象创建后, 紧接着执行的语句为:
t1.start();//启动线程 A
start方法创建了运行线程所必需的系统资源, 并调用线程
的 run方法 。 start方法返回之后, 线程就进入可运行
(Runnable)状态 。
10.3.3 线程运行
? 处于运行状态 (Running)的线程也可能被调
度到可运行状态 (Runnable)-例如所分配的
CPU时间片结束或是调用 yield方法主动让
出 CPU-从而给其他处于可运行状态的线
程以执行机会 。
10.3.4 线程阻塞
? 当以下任一事件发生时, 正在运行的线程将由运
行状态转化为阻塞 (Blocked)状态:
(1)休眠方法被调用
(2)线程调用 wait方法,并且等待一个指定的条件
被满足
(3)线程在 I/O处阻塞
? 当线程处于阻塞状态时, 即使处理器处于空闲状
态, 该线程也不会被执行 。 此外, 当线程 t1睡眠
结束, 也并不立刻进入运行状态, 而是先进入可
运行状态, 等候调度 。
10.3.5 终止线程
? 线程的 run方法退出后, 自然进入死亡 (Dead)状态 。
? ! 虽然也可以调用线程的 stop方法来强制杀死一个线程,
但是不推荐使用 。
? 此外, 如果想知道线程是否处于活动状态, 可以用 isAlive
方法来判断 。 isAlive在线程已经启动 (start)并且没有死亡
(Dead)时返回值是 true。 假如该方法返回的是 false,那么
可以知道这个是新线程 (已创建, 还没有进入可运行状态 )
或者当前线程已经死亡 。
10.4 线程中断
? 我们已经知道, 当线程的 run方法运行结束, 该线程也就
自然终止 。 虽然也可以调用线程的 stop方法来强制杀死一
个线程, 但该方法是不安全的, 因此不推荐使用 。 Java中
还提供了 interrupt方法用来中断一个线程 。 当调用一个
线程的 interrupt方法时, 即向该线程发送一个中断请求,
并将该线程的, interrupted”状态值置为 true。
? 除了 interrupted方法外, 还有一个 isInterrupted方法 。
这两个方法均用于判断当前线程是否已经被中断请求过,
不同之处在于,interrupted方法在返回, interrupted”
状 态 值 的 同 时 会 将 其 值 重 新 设 置 为 false, 而
isInterrupted方法只是简单返回, interrupted”的状态
值 。
10.5 线程的优先级
? 当线程被创建时, 其优先级是由创建它的线程的
优先级所决定的 。
? 可 以 在 线 程 创 建 之 后 的 任 意 时 刻 通 过 调 用
setPriority方法来修改线程的优先级 。
? 线程的优先级是在 MIN_PRIORITY( 值为 1) 和
MAX_PRIORITY(值为 10)范围内的一个整数值 。 数
值越大, 代表线程的优先级越高 。
? 见 mod10\PriorityExample.java
例 10.4
public class PriorityExample{
public static void main(String []args){
Thread a=new PThread("A");
Thread b=new PThread("B");
a.setPriority(7);
b.setPriority(1);
a.start();
b.start();
}
}
class PThread extends Thread{
public PThread(String n){
super(n); //线程名称
}
public void run(){
for(int i=0;i<5000000;i++){
if(i%500000==0) //输出线程名称
System.out.print(getName());
}}}
10.5.2利己线程
? 在线程中可以通过调用 sleep方法, 放弃当前线程对处理器的使用,
从而使得各个线程均有机会得到执行 。 但是有的时候, 也有线程可能
不遵循这个规则 。 for循环是一个紧密循环 (tight loop)。 一旦运行
系统选择了有这种循环体的线程执行, 该线程就不会主动放弃对处理
器的使用权, 除非 for循环自然终止或者该线程被一个有更高优先级
的线程抢占 。 这样的线程, 我们称为利己线程 。
? 有些情况下, 利己线程并不会引起问题, 但是在某些情况下, 利己线
程长久占有处理器的使用权, 就会让其他的线程在得到处理器的使用
权之前等待一个很长的时间 。
10.5.3分时方式
? 为了解决在利己线程可能长时间占据 CPU
的问题,有些系统通过分时方式 (time-
slicing)来限制利己线程的执行 。
? 在分时方式中,处理器的分配按照时间片来
划分,对于那些有相同最高优先级的多个线
程,分时技术会交替地分配 CPU时间片给
它们执行。当分配给一个线程 (即使该线程
是利己线程 )的时间片结束时,即使该线程
没有运行结束,也会让出 CPU的使用权。
10.6 线程同步
? 线程必须关注与其并发执行的其他线程的状
态和行为 。 一个很典型的例子就是生产者 /
消费者问题 。
? 在线程同步我们主要讲:
?对象锁
?可重入锁
?notifyAll和 wait方法
对象锁
? 为了使得线程 p和线程 c不能同时对 box对象进行操作,我
们在 put和 get方法前加上 synchronized(同步 )关键字 。
? Java运行系统为每个对象分配了唯一的对象锁 (object
lock),当一个线程调用对象中被同步的方法来访问对象
时,这个对象就会被锁定。在该线程退出同步方法 (交出
对象锁 )之前,其他线程不能再调用该对象中任何被同步
的方法。
? !对象锁的取得和释放由 Java运行系统自动完成,但始终
遵循以下规则:在任何时刻,一个对象的对象锁至多只能
被一个线程拥有。
可重入锁
? Java中的对象锁是可重入的,即 Java运行系统允许一个线
程再次取得已为自己控制对象锁。锁的可重入性非常重要,
这可以防止一个线程的死锁。当一个线程 (不妨称为 t)调
用 Reentrant类型对象 (不妨称为 r)中的方法 a时,对象 r被
锁定。此时 a又调用 b,由于 b也是一个同步的方法,因此
线程 t需要再次取得对象 r的对象锁,才能执行方法 b。由
于 Java运行系统支持锁的重入,线程 t可以再次取得对象 r
的对象锁,从而方法 b能够得到执行。
? !如果不支持锁的重入,调用同步方法就有可能引起死锁 。
notifyAll和 wait方法
? 方法 wait使得当前线程进入阻塞状态,同时交出
对象锁,从而其他线程就可以取得对象锁。
? 方法 notifyAll唤醒的是由于等待 notifyAll方法
所在对象 (这里是 box)而进入等待状态的所有线程,
被唤醒的线程会去竞争对象锁,当其中某个线程
得到锁之后,其他的线程重新进入阻塞状态。
Java实例 —— (续)
本例中包括:
?Box.java
?Producer.java
?Consumer.java
?ProducerConsumerTest.java
Box.java
public class Box{
private int value;
private boolean available=false;
public synchronized int get() {
while (available == false) {
try {
wait(); //等待生产者写入数据
} catch (InterruptedException e) {
e.printStackTrace(); }
}
available = false;
// 通知生产者数据已经被取走,可以再次写入数据
notifyAll();
return value;
}
public synchronized void put(int value) {
while (available == true) {
try {
wait(); // 等待消费者取走数据
} catch (InterruptedException e) {
e.printStackTrace(); }
}
this.value = value;
available = true;
//通知消费者可以来取数据
notifyAll();
}
}
Consumer.java
public class Consumer extends Thread {
private Box box;
private String name;
public Consumer(Box b,String n) {
box=b;
name=n;
}
public void run() {
int value = 0;
for (int i = 1; i < 6; i++) {
value = box.get();
System.out.println("Consumer " +name + " consumed," + value);
try { sleep((int)(Math.random() * 100));
} catch (InterruptedException e) {e.printStackTrace(); }
} }}
ProducerConsumerTest.java
public class ProducerConsumerTest {
public static void main(String[] args) {
Box box = new Box();
Producer p= new Producer(box,"p");
Consumer c= new Consumer(box,"c");
p.start();
c.start();
}
}
习题
10.1
10.2
10.3
谢谢
The End