第7章 多线程 北京大学计算机系 代亚非
第7章 多线程 7.1 多线程基本概念 7.2 创建线程的方式 7.3 线程的挂起与唤醒 7.4 多线程问题 7.5 小结
7.1 多线程基本概念 多线程的任务 传统的进程 文件 各种系统资源 输入输出装置 文件 各种系统资源 输入输出装置 数据区段 数据区段 7.1 多线程基本概念 文件 各种系统资源 输入输出装置 文件 各种系统资源 输入输出装置 数据区段 数据区段 程序区段 程序区段 同时有数个地方在执行 只有一个地方在执行 多线程的任务 传统的进程
7.1 多线程基本概念 多线程的优势: 减轻编写交互频繁、涉及面多的程序的困难. 程序的吞吐量会得到改善. 7.1 多线程基本概念 多线程的优势: 减轻编写交互频繁、涉及面多的程序的困难. 程序的吞吐量会得到改善. 由多个处理器的系统,可以并发运行不同的线程.(否则,任何时刻只有一个线程在运行)
7.1 多线程基本概念 线程与进程的区别: 多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响. 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
7.1 多线程基本概念 对线程的综合支持是Java技术的一个重要特色.它提供了thread类、监视器和条件变量的技术. 7.1 多线程基本概念 对线程的综合支持是Java技术的一个重要特色.它提供了thread类、监视器和条件变量的技术. 虽然Macintosh,Windows NT,Windows 9等操作系统支持多线程,但若要用C或C++编写多线程程序是十分困难的,因为它们对数据同步的支持不充分.
7.2 创建线程的方式 1. public class mythread extends Applet implements Runnable (小应用或已经是某个类的子类时) 2. 继承类Thread public class mythread extends Thread 3. 上述两种方法中都可用类Thread产生线程的对象 Thread newthread; 4. 创建并启动线程 newthread=new Thread(this); newthread.start();
7.2 创建线程的方式 5. run方法是运行线程的主体,启动线程时,由java直接调用 public void run() 7.2 创建线程的方式 5. run方法是运行线程的主体,启动线程时,由java直接调用 public void run() 6.停止线程,由小应用程序的stop调用线程的stop newthread.stop() 7 sleep方法的作用,暂停线程的执行,让其它线程得到机会,sleep要丢出异常,必须抓住. Try{sleep(100)}catch(InterruptedException e){} 例:小应用程序中不用Runnable接口仍然可以使用线程(不调用主类的方法和调用主类的方法)
7.2 创建线程的方式 import java.applet.*; public class thread extends Applet 7.2 创建线程的方式 import java.applet.*; public class thread extends Applet { mythread t1=new mythread(); public init() { t1.start();} class mythread extends Thread { public void run() { for (int i=0;i<4;i++) System.out.println( “”+i); { try{sleep(400); } catch(InteruptedException e){ } }
7.2 创建线程的方式 public class mainclass extends Applet { C t1=new C(this); public void init() { t1.start();} public void paint(Graphics g) { g.drawString("Hello,java",10,50);}} class C extends Thread { mainclass a; C(mainclass b) { a=b; } public void run() { while(true){ a.repaint(); try{sleep(400);} catch(InterruptedException e){} }} 7.2 创建线程的方式
7.2 创建线程的方式 8.其它常用的方法 isAlive :判断线程目前是否正在执行状态中 7.2 创建线程的方式 8.其它常用的方法 isAlive :判断线程目前是否正在执行状态中 if(newthread.isAlive()) newthread.stop(); resume:要求被暂停得线程继续执行 suspend:暂停线程的执行 join:等待线程执行完毕 thatThread.join();被等待的那个线程不结束,当前线程就一直等待. yield:将执行的权力交给其它线程,自己到队列的最后等待.
7.2 创建线程的方式 9.线程的优先权 某一时刻只有一个线程在执行,调度策略为固定优先级调度. 7.2 创建线程的方式 9.线程的优先权 某一时刻只有一个线程在执行,调度策略为固定优先级调度. newthread.setPriority(Thread.MIN_PRIORITY) 级别有:MIN-PRIORITY NOM_PRIORITY MAX-PRIORITY 10. 自私的线程:有很高的优先权的线程,不主动睡眠或让出处理器控制权.
7.2 创建线程的方式 11. 线程的状态 new Thread() New Thread Runnable start() 7.2 创建线程的方式 11. 线程的状态 new Thread() New Thread Runnable start() Not Runnable stop() Dead yield() stop() or run()exit . suspend() sleep() wait() resume()
7.2 创建线程的方式 当一个线程执行完所有语句后就自动终止,调用线程的stop()方法,也可以强制终止线程。 7.2 创建线程的方式 当一个线程执行完所有语句后就自动终止,调用线程的stop()方法,也可以强制终止线程。 如果希望线程正常终止,可采用标记来使线程中的run()方法退出。
7.2 创建线程的方式 public class Xyz implements Runnable 7.2 创建线程的方式 public class Xyz implements Runnable { private boolean timeToQuit=false; public void run() { while (!timeToQuit) {…..} //clean up before run() ends; } public void stopRunning() { timeToQuit=true;}
7.2 创建线程的方式 public class ControlThread { private Runnable r=new Xyz(); 7.2 创建线程的方式 public class ControlThread { private Runnable r=new Xyz(); private Thread t=new Thread(r); public void startThread() { t.start(); } publi void stopThread() { r.stopRunning();} }
7.3 线程的挂起与唤醒 暂停线程的执行等待条件满足再执行. 下面的例子显示线程的挂起和唤醒 小应用程序第一次开始时,线程被启动 7.3 线程的挂起与唤醒 暂停线程的执行等待条件满足再执行. 下面的例子显示线程的挂起和唤醒 小应用程序第一次开始时,线程被启动 浏览器改变页面时,小应用程序的stop()方法被调用,线程被挂起. 浏览器回到原来的页面时,线程被唤醒.
7.3 线程的挂起与唤醒 public void start() { if (mythread==null) 7.3 线程的挂起与唤醒 public void start() { if (mythread==null) {mythread=new Thread(); mythread.start();} else { mythread.resume();} } public void run() { while(true){ try{sleep(100);} catch(InterruptedException e) {}} public void stop() { mythread.suspend(); }.
7.4 多线程问题---执行的顺序 多个线程运行时,调度策略为固定优先级调度.级别相同时,由操作系统按时间片来分配 7.4 多线程问题---执行的顺序 多个线程运行时,调度策略为固定优先级调度.级别相同时,由操作系统按时间片来分配 下面给出的例子中,共运行三个线程,它们做同样的事, 每次打印循环次数和自己的序列号,运行结果表明,它们并不是连续运行的. 在上例中如果给某个线程赋予较高的优先权,则发现这个进程垄断控制权 thread.setPriority(Thread.MAX_PRIORITY) thread\multithread.class--f1.bat thread\Priority.class---f2.bat
7.3 多线程问题 //多个进程运行时执行顺序是交叉的 class multithread extends Thread { int threadNum; public static void main(String args[]) { multithread array[]=new multithread[3]; for (int i=0;i<3;i++) array[i]=new multithread(i); for (int i=0;i<3;i++) array[i].start(); } multithread(int SerialNum) { super(); threadNum=SerialNum; } public void run() { for(int j=0;j<5;j++) System.out.println(“<"+j+"> +MySerialNum); System.out.println("thread "+threadNum+ "bye.");}} 7.3 多线程问题
7.4 多线程问题---如何写多线程 1.分别定义不同的线程类,在各自的run方法中定义线程的工作 7.4 多线程问题---如何写多线程 1.分别定义不同的线程类,在各自的run方法中定义线程的工作 class mythread1 extends Thread { public void run{….} } class mythread2 extends Thread 2. 在主类中实例化各线程类,并启动线程. public class demo extends Applet { public void init() { mythread t1=new mythread1(); mythread t2=new mythread2(); t1.start(); t2.start();} }
7.4 多线程问题---如何写多线程 练习:将窗口分为上下两个区,分别运行两个线程,一个在上面的区域中显示由右向左游动的字符串,另一个在下面的区域从左向右游动的字符串. 方法一: 一个线程,在paint方法中使用两个输出字符串的语句 public void paint(Graphics g) { if y1<0 y1=200 else y1=y1-10; if y2>200 y2=0 else y2=y2+10; g.drawString(“hello, Java!”,20,y1,); g.drawString(“hello, Java!”,40,y2,); }
7.4 多线程问题---如何写多线程 方法二:定义两个类,运行各自的线程,各自有自己的paint()方法. 7.4 多线程问题---如何写多线程 方法二:定义两个类,运行各自的线程,各自有自己的paint()方法. 注意: 两个小应用程序必须是panel类或者是canvas类,将小应用的区域分成两块,否则不能运行paint语句.
7.4 多线程问题---线程间的通信 1. 线程间的通信可以用管道流,. 创建管道流: 7.4 多线程问题---线程间的通信 1. 线程间的通信可以用管道流,. 创建管道流: PipedInputStream pis=new PipedInputStream(); PipedOutputStream pos=new PipedOutputStream(pis); 或: PipedOutputStream pos=new PipedOutputStream(); PipedInputStream pis=new PipedInputStream(pos); 线程1 PipedOutputStream PipedInputStream 输出流outStream 输入流inStream 线程2
7.4 多线程问题---线程间的通信 管道流不能直 接读写 PrintStream p = new PrintStream( pos ); 7.4 多线程问题---线程间的通信 管道流不能直 接读写 PrintStream p = new PrintStream( pos ); p.println(“hello”); DataInputStream d=new DataInputStream(pis); d.readLine(); 2. 通过一个中间类来传递信息. printStream DataInputStream 线程2 线程1 中间类m s m.write(s) s=m.read() write() read()
7.4 多线程问题--线程间的通信 管道流可以连接两个线程间的通信 下面的例子里有两个线程在运行,一个往外输出信息,一个读入信息. 将一个写线程的输出通过管道流定义为读线程的输入. outStream = new PipedOutputStream(); inStream = new PipedInputStream(outStream); new Writer( outStream ).start(); new Reader( inStream ).start();
7.4 多线程问题--线程间的通信 (thread\Pipethread.class--f3.bat) 主类Pipethread 作为参数传给Writer Writer( outStream ) 辅类Writer 线 程 类 管 道 流 辅类 Reader 线 程 类 将数据写 到输出流 输入流 从流中读数据
7.4 多线程问题--线程间的通信 public class Pipethread { public static void main(String args[]) { Pipethread thisPipe = new Pipethread(); thisPipe.process(); } public void process() { PipedInputStream inStream; PipedOutputStream outStream; PrintStream printOut; try{ outStream = new PipedOutputStream(); inStream = new PipedInputStream(outStream); new Writer( outStream ).start(); new Reader( inStream ).start(); }catch( IOException e ){ } } 7.4 多线程问题--线程间的通信 .
7.4 多线程问题---线程间的通信 class Reader extends Thread { private PipedInputStream inStream;//从中读数据 public Reader(PipedInputStream i) { inStream = i; } public void run() { String line; DataInputStream d; boolean reading = true; try{ d = new DataInputStream( inStream ); while( reading && d != null){ try{line = d.readLine(); if( line != null ){ System.out.println( ”Read: " + line ); } else reading = false; }catch( IOException e){ } } catch( IOException e ){ System.exit(0); } try{ Thread.sleep( 4000 );} catch( InterruptedException e ){}}} 7.4 多线程问题---线程间的通信
7.4 多线程问题--线程间的通信 class Writer extends Thread { private PipedOutputStream outStream;//将数据输出 private String messages[ ]= { "Monday", "Tuesday ", "Wednsday", "Thursday","Friday :", "Saturday:","Sunday :"}; public Writer(PipedOutputStream o) { outStream = o; } public void run() { PrintStream p = new PrintStream( outStream ); for (int i = 0; i < messages.length; i++) { p.println(messages[ i ]); p.flush(); System.out.println("WrIte:" + messages[i] ); } p.close(); p = null; }} 7.4 多线程问题--线程间的通信 .
7.3 多线程问题---资源协调 1. 数据的完整性 变量 余额 withdrwal() 透支 线程1 取过来 资源 加1后送回去 线程2 线程10 资源 取过来 加1后送回去 withdrwal() 透支 余额 变量
7.3 多线程问题---资源协调 对共享对象的访问必须同步,叫做条件变量. Java语言允许通过监视器(有的参考书称其为管程)使用条件变量实现线程同步. 监视器阻止两个线程同时访问同一个条件变量.它的如同锁一样作用在数据上. 线程1进入withdrawal方法时,获得监视器(加锁);当线程1的方法执行完毕返回时,释放监视器(开锁),线程2的withdrawal方能进入. withdrawal() 线程1 监视器 线程2
7.3 多线程问题---资源协调 用synchronized来标识的区域或方法即为监视器监视的部分。 一般情况下,只在方法的层次上使用关键区 read write 监 视 器 线程1 线程2
7.3 多线程问题---资源协调 此处给出的例子演示两个线程在同步限制下工作的情况. class Account { statics int balance=1000; //为什么用static? statics int expense=0; public synchronized void withdrawl(int amount) { if (amount<=balance) { balance-=amount; expense+=amount;} else { System.out.println(“bounced: “+amount);} }
7.3 多线程问题---资源协调 2. 等待同步数据 可能出现的问题: 生产者比消费者快时,消费者会漏掉一些数据没有取到 共享对象 write read 可能出现的问题: 生产者比消费者快时,消费者会漏掉一些数据没有取到 消费者比生产者快时,消费者取相同的数据. notify()和wait ()方法用来协调读取的关系. notify()和wait ()都只能从同步方法中的调用.
7.3 多线程问题---资源协调 notify的作用是唤醒正在等待同一个监视器的线程. wait的作用是让当前线程等待 信息版例子 read()方法在读信息之前先等待,直到信息可读,读完后通知要写的线程. write()方法在写信息之前先等待,直到信息被取走,写完后通知要读的进程. DemoWait.class--->f4.bat
7.3 多线程问题---资源协调 writer reader aaaa aaaa aaaa aaaa aaaa aaaa aaaa bbbbb bbbbb bbbbb cccc cccc cccc cccc cccc cccc
7.3 多线程问题---资源协调 class WaitNotifyDemo { public static void main(String[] args) { { MessageBoard m = new MessageBoard(); Reader readfrom_m = new Reader(m); Writer writeto_m=new Writer(m); readfrom_m.start(); writeto_m.start(); }
7.3 多线程问题---资源协调 class MessageBoard { { private String message; private boolean ready = false;(信号灯) public synchronized String read() { while (ready == false) { try { wait(); } catch (InterruptedException e) { } } ready = false; notify(); //起始状态先写后读 return message; } public synchronized void write(String s) { while (ready == true) { try { wait(); } catch (InterruptedException e) { } } message = s; ready = true; notify(); }} 7.3 多线程问题---资源协调
7.3 多线程问题---资源协调 class Reader extends Thread { private MessageBoard mBoard; public Reader(MessageBoard m) { mBoard = m; } public void run() { String s = " "; boolean reading = true; while( reading ){ s = mBoard.read(); System.out.println("Reader read: " + s); if( s.equals("logoff") ) reading = false; } System.out.println("Finished: 10 seconds..."); try{ sleep( 10000 ); } catch (InterruptedException e) { } } } 7.3 多线程问题---资源协调
7.3 多线程问题---资源协调 class Writer extends Thread { private MessageBoard mBoard; private String messages[ ]= { "Monday :------------------------", “…..”, "Sunday : ----------------------"}; public Writer(MessageBoard m) { mBoard = m; } public void run() { { for (int i = 0; i < messages.length; i++) { mBoard.write(messages[ i ]); System.out.println("Writer wrote:" + messages[i] ); try { sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { } } mBoard.write("logoff"); } } 7.3 多线程问题---资源协调
7.3 多线程问题---资源协调 多线成问题---资源的协调和锁定 1. 死锁问题 如果你的持有一个锁并试图获取另一个锁时,就有死锁的危险. 解决死锁问题的方法:给条件变量施加排序 线程2 pen 线程1 note 把“pen”给我,我 才能给你“note” 把“note”给我,我 才能给你“pen”
7.3 多线程问题---daemon线程 什么是daemon(守护)? 在客户/服务器模式下,服务器的作用是等待用户发来请求,并按请求完成客户的工作 守护线程是为其它线程提供服务的线程 守护线程一般应该是一个独立的线程,它的run()方法是一个无限循环. 守护线程与其它线程的区别是,如果守护线程是唯一运行着的线程,程序会自动退出 request 服务器端 客户端 daemon
7.4 小结 1. 实现线程有两种方法: 实现Ruannable接口 继承Thread类 2. 在小应用中通常在start中创建线程 3.当新线程被启动时,java调用该线程的run方 法,它是Thread的核心. 4. 线程由四个状态:新生,运行,暂停,死亡 5. 线程间的通信方式由三种:完全共享数据,通过监视器,通过join.
7.4 小结 6. 两个或多个线程竞争资源时,需要用同步的方法协调资源. 7. 多个线程执行时,要用到同步方法,即使用 synchronized的关键字设定同步区 8. wait和notify起协调作用 9. 守护进程的特点是当程序中制胜它自己时,会自动中止.
作业 创建两个线程的实例,分别将一个数组从小大大和从达到小排列.输出结果.