Transcript 2 线程

JAVA多线程操作
信息学院
李 峰
内容提纲
1 线程的概念
2 线程的状态
3 如何创建线程
4 线程的调度及其控制方法
5 多个线程如何进行互斥和同步
2
学习目标

如何采用java线程实现多道程序设计

学习java中线程的使用,掌握线程的调度和控
制方法,理解多线程互斥和同步的实现原理和
机制,以及多线程的应用。

掌握线程间的调度关系,尤其是通过线程睡眠
机制使其它等待线程获得执行机会。
3
难点和重点

多线程的调度和控制

多线程的互斥和同步
4
1 多线程基本概念
文件
各种系统资源
输入输出装置
文件
各种系统资源 输入输出装置
数据空间
数据空间
程序区段
程序区段
只有一个地方在执行
传统的进程
同时有数个地方在执行
多线程的任务
5
1 多线程基本概念

一个线程就是一个程序内部的顺序控制流。

线程与进程的区别:
1 每个进程的内部数据和状态都是完全独立的,进程
切换的开销大。
2 线程,轻量级的进程,同一类线程共享一块内存空
间和一组系统资源, 线程本身的数据通常只有寄存器
数据,以及一个程序执行时使用的堆栈,所以线程的
切换比进程切换的负担要小。
3 多进程, 在操作系统中,能同时运行多个任务。
4 多线程,在同一个应用程序中,有多个并发的顺序
流同时执行。
6
1 多线程基本概念
多线程的优势:
 减轻编写交互频繁、涉及面多的程序的困难
 程序的吞吐量会得到改善,能提高资源使用
效率
 由多个处理器的系统,可以并发运行不同的线
程.(否则,任何时刻只有一个线程在运行)
7
1 多线程基本概念
线程体

Java的线程是通过java.lang.Thread类来实现的。

每个线程都是通过某个特定Thread对象的run()方
法来完成其操作的,方法run()称为线程体。

class MyThread extends Thread {
public void run() {
// 这里写上线程的内容
}
8
2 线程的状态

创建状态(new):线程对象已经创建,但尚未启动,
所以不可运行。

可运行状态(runnable):所有资源都准备好了,就
差cpu资源,一旦线程调度器分配cpu资源给该线程,
立刻开始执行。

死亡状态(dead):线程体执行完毕,即run()方法
运行结束。

堵塞状态(not runnable):不仅缺乏cpu资源,还缺
乏其它资源。
9
2 线程状态
yield()
new Thread()
start()
New Thread
stop()
suspend()
sleep()
wait()
. .
Runnable
.
Not Runnable
stop() or
run()exit
resume()
stop()
Dead
10
2 线程状态
创建状态(new Thread)
Thread myThread=new MyThreadClass()
(注意: MyThreadClass 是Thread的子类)
 可运行状态(Runnable)
Thread myThread=new MyThreadClass()
myThread.start( )
 死亡状态(Dead)
自然撤消(线程执行完毕)

11
2 线程状态

下面几种情况下,当前线程会放弃cup,进入
阻塞状态(Not runnable):
(1) 线程调用sleep()方法主动放弃执行权;
(2) 由于当前线程进行I/O访问,外存读写,等
待用户输入等操作,导致线程阻塞;
(3) 为等候一个条件变量,线程调用wait()方
法
(4) 线程试图调用另一个对象的“同步”方法,
但那个对象处于锁定状态,暂时无法使用。
12
3 线程的创建






Thread(ThreadGroup group,Runnable target,
String name)
Thread(Runnable target, String name)
Thread(Runnable target)
Thread(String name)
Thread()
构造新的 Thread 对象,以便将 target 作为其运行对
象,将指定的 name 作为其名称,并作为 group 所引
用的线程组的一员。
任何实现接口Runnable的对象都可以作为一个线程的
目标对象。
13
3 线程的创建
创建线程的两种方法:
1 定义一个线程类,它继承类Thread并重写其
中的方法run();
2 提供一个实现接口Runnable的类作为线程的
目标对象,把目标对象传递给这个线程实例,
由该目标对象提供线程体run()。
14
3 线程的创建
通过继承类Thread构造线程
public class ThreadDemo1 extends Thread {
public ThreadDemo1(String str) {
super(str); }
public void run() {
for (int i = 0; i<5; i++) {
System.out.println(i+" "+getName());
try{
sleep((int)(Math.random()*1000));
}catch(InterruptedException e) { }
}
System.out.println("end!"+getName() );
}
}
15
public class ThreadTest {
public static void main(String[] args) {
Thread T1=new ThreadDemo1("First");
Thread T2=new ThreadDemo1("Second");
T1.start();
T2.start();
}}
执行结果:
0 Second
0 First
1 First
2 First
1 Second
2 Second
3 First
3 Second
4 Second
4 First
end!First
end!Second
16
3 线程的创建
通过实现接口构造线程
public class MyThread implements Runnable {
public int time;
public String name;
public MyThread(int time, String str) {
this.time=time;
this.name=str;
}
public void run() {
for (int i=0;i<4;i++){
System.out.println(" "+i+"= "+this.name);
try{Thread.sleep(this.time);}
catch(InterruptedException e){ }
} }}
17
public class ThreadTest2 {
public static void main(String[] args) {
Thread mt1=new Thread(new
MyThread(400,"rod"));
Thread mt2=new Thread(new
MyThread(1000,"bos"));
mt1.start();
mt2.start();
}}
执行结果:
0= rod
0= bos
1= rod
2= rod
1= bos
3= rod
2= bos
3= bos
18
3 线程的创建
两种方法的比较:
1 使用Runnable接口,可以从其他类继承,保
持程序风格的一致性;
2 直接继承Thread类,不能再从其他类继承,
但编写简单,可以直接操纵线程。
19
4 线程的调度和控制方法
4.1线程的调度

Java提供了一个线程调度器来监控程序中启动
后进入就绪状态的所有线程。线程调度器按照
线程的优先级决定应调度哪些线程来执行。

线程调度是抢先式的,按照优先级来调度:
(1)时间片方式
(2)非时间片方式
20
4 线程的调度和控制方法
4.2 线程的优先级
某一时刻只有一个线程在执行,调度策略为固定
优先级调度。线程的优先级用数字来表示,范
围从1到10,级别有:
MIN-PRIORITY 1
NOM_PRIORITY 5 缺省优先级
MAX-PRIORITY 10
Thread.setPriority(Thread.MIN_PRIORITY)
int Thread.getPriority();
21
22
public class ThreadDemo2 extends Thread {
String name;
public ThreadDemo2(String str) {
this.name=str; }
public void run() {
for (int i = 0; i<3; i++) {
System.out.println(this.name+"
"+i+"="+getPriority());
}
}
}
23
public class ThreadTest2 {
public static void main(String[] args) {
Thread T1=new ThreadDemo2("T1");
Thread T2=new ThreadDemo2("T2");
ThreadDemo2 T3=new ThreadDemo2("T3");
T1.setPriority(1);
T2.setPriority(10);
T3.setPriority(10);
T1.start();
T2.start(); T3.start(); } }
执行结果:
T2 0=10
T2 1=10
T2 2=10
T3 0=10
T3 1=10
T3 2=10
T1 0=1
T1 1=1
T1 2=1
24
4 线程的调度和控制方法
线程调度注意的问题:

注意:并不是在所有系统中运行java程序时都
采用时间片策略调度线程,所以一个线程在空
闲时应该主动放弃cpu,以使其它同优先级
(调用yield()方法)和低优先级(调用sleep
()方法)的线程得到执行。
25
4 线程的调度和控制方法
4.3基本的线程控制
终止线程
线程执行完其run()方法后,自然终止。
 测试线程状态
可以通过Thread中的isAlive()方法来获取线程是否
处于活动状态;
线程由start()方法启动后,直到其被终止之间的任
何时刻,都处于Alive状态。

26
4 线程的调度和控制方法

线程的暂停和恢复
sleep()、join()方法
当前线程等待调用该方法的线程结束后,再恢
复执行。
TimerThread tt=new TimerThread(100);
tt.start();
……
public void timeout(){
tt.join();//等待线程tt执行完后再继续执行
……}
27
4 线程的调度和控制方法

yield()方法
调用该方法的线程把自己的控制权让出来,线
程调度器把该线程放到同一优先级的Runnable
队列的最后,然后从该队列中取出下一个线程
执行。该方法是给同优先级的线程以执行的机
会,如果同 优先级的Runnable队列中没有其
他线程,则该线程继续执行。
28
5 多线程互斥与同步
1. 数据的完整性
线程1
资源
变量
线程2
线程10
push()
stack
pop()
push()
29
5 多线程的互斥与同步
临界资源
public class stack {
int idx=0;
char[] data=new char[6];
public stack() { }
public void push(String c){
if(idx<6){
data[idx]=c; idx++; } }
public String pop(){
if(idx>0) {
idx--; return data[idx];
}else {return "stack is null";
}
}} }
两个线程A和B在同时使用stack的同一个对象,A正在往
堆栈里push一个数据,B则要从堆栈中pop一个数据
30
5 多线程的互斥与同步
操作之前data=|p|q| | | | | idx=2
 A执行push中的第一个语句,将r推入堆栈;
data=|p|q|r| | | | idx=2
 A还未执行idx++语句,A的执行被B中断,B执行pop
方法,返回q:
data=|p|q|r| | | | idx=1
 A继续执行push语句的第二个语句:
data=|p|q|r | | | | idx=2
最后的结果相当于r没有入栈
产生这种问题的原因在于对共享数据访问的操作的不完
整性。

31
5 多线程的互斥与同步

在java语言中,引入了对象互斥锁的概念,来
保证共享数据操作的完整性。
1 每个对象都对应一个可称为“互斥锁”的标
记,这个标记用来保证在任一时刻,只能有一
个线程访问对象。
2 关键字synchronized来与对象互斥锁联系。
当某个对象用synchronized修饰时,表明该对
象在任一时刻只能由一个线程访问。
32
public void push(String c){
synchronized(this){
if(idx<6){
data[idx]=c;
idx++;
}
}
}
public String pop(){
synchronized(this){
if(idx>0) {
idx--;
return data[idx];
}else {
return "stack is null";
}
}
}
33
5 多线程的互斥与同步
Synchronized除了像上面讲的放在对象前面限
制一段代码的执行外,还可以放在方法声明中,
表示整个方法为同步方法。
public synchronized void push (char c)
{ …… }
如果synchronized用在类声明中,则表明该类中
的所有方法都是synchronized 的。

34
5 多线程的互斥与同步


用synchronized来标识的区域或方法即为监视
器监视的部分。
一个类或一个对象由一个监视器,如果一个程
序内有两个方法使用synchronized标志,则他
们在一个监视器管理之下.
线程1
push

监
视
器
线程2
pop
一般情况下,只在方法的层次上使用关键区
35
5 多线程的互斥与同步


监视器阻止两个线程同时访问同一个条件变量
或方法,它如同锁一样作用在数据上.
线程1进入push方法时,获得监视器(加锁);当线
程1的方法执行完毕返回时,释放监视器(开锁),
线程2的方能进入pop方法.
监视器
线程2
线程1
stack
36
5 多线程的互斥与同步
2. 等待同步数据
.
生产者
write.
共享对象
消费者
read
可能出现的问题:
• 生产者比消费者快时,消费者会漏掉一些数据
没有取到
• 消费者比生产者快时,消费者取相同的数据.
• notify()和wait ()方法用来协调读取的关系.
• notify()和wait ()都只能从同步方法中的调用.
37
多线程的同步
public class stack {
private int idx=0;
private char[] data=new char[6];
public stack() { }
public synchronized void push(char c){
while (idx==data.length)
{
try{ this.wait();
}catch(InterruptedException e){} }
data[idx]=c;
System.out.println("Push:"+c);
idx++; this.notify();}
public synchronized char pop(){
while (idx==0){
try{ this.wait();
}catch(InterruptedException e){} }
this.notify();
idx--; return data[idx]; } }
38
生成者和消费者问题
public class Push extends Thread{
stack s;
public Push(stack s) {
this.s=s; }
public void run() {
char c;
for (int i = 0; i<6; i++) {
c=(char)(Math.random()*26+'A');
s.push(c);
//System.out.println("Push:"+c);
try{
sleep((int)(Math.random()*1000));
}catch(InterruptedException e){
}} }}
39
public class Pop extends Thread {
stack s;
public Pop(stack s) {
this.s=s;
}
public void run() {
for (int i = 0; i<6; i++)
{
char c=s.pop();
System.out.println("pop:"+c);
try{
sleep((int)(Math.random()*1000));
}catch(InterruptedException e){
}
}} }
40
public class ThreadTest {
public static void main(String[] args) {
stack s=new stack();
Thread push1=new Push(s);
Thread pop1=new Pop(s);
push1.start();
pop1.start(); } }
执行结果:
Push:J
pop:J
Push:K
Push:A
pop:A
Push:R
Push:Y
Push:T
pop:T
pop:Y
pop:R
pop:K
41
5 多线程的互斥与同步

notify的作用是唤醒正在等待同一个监视器的
线程.

wait的作用是让当前线程等待
42
5 多线程的互斥与同步
wait(), notify(), notifyAll()
(1) wait,noitfy,notifyAll必须在已经持有锁的情
况下执行,所以它们只能出现在synchronized
作用的范围内。
(2) wait释放已持有的锁,进入wait队列。
(3) notify唤醒wait队列中的第一个线程并把它
移入申请队列。
(4) notifyAll唤醒队列中的所有的线程并把它们
移入申请队列。
43
5 多线程的互斥与同步
resume():要求被暂停得线程继续执行
suspend():暂停线程的执行
在JDK1.2中不再使用resume和 suspend,其相
应功能由wait()和notify()来实现。
44
5 多线程的互斥与同步
stop()方法:
 当一个线程执行完所有语句后就自动终止,调
用线程的stop()方法,也可以强制终止线程。
 但在JDK1.2中不再使用stop(),而是采用标
记来使线程中的run()方法退出。
45
5 多线程的互斥与同步
public class Xyz implements Runnable
{ private boolean timeToQuit=false;
public void run()
{ while (!timeToQuit)
{…..}
//clean up before run() ends;
}
public void stopRunning()
{ timeToQuit=true;}
}
46
5 多线程的互斥与同步
public class ControlThread
{ private Runnable r=new Xyz();
private Thread t=new Thread(r);
public void startThread()
{ t.start(); }
publi void stopThread()
{ r.stopRunning();}
}
47
6 小结
1. 实现线程有两种方法:
 实现Ruannable接口
 继承Thread类
2. 当新线程被启动时,java调用该线程的run方
法,它是Thread的核心.
3. 线程由四个状态:创建,运行,暂停,死亡.
48
6 小结
4. 两个或多个线程竞争资源时,需要用同步的方法
协调资源.
5. 多个线程执行时,要用到同步方法,即使用
synchronized的关键字设定同步区
6. wait和notify起协调作用
49
50