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