第九章 多线程程序设计

Preview:

DESCRIPTION

第九章 多线程程序设计. §9.1 线程的概念 §9.2 Java 线程的创建 §9.3 线程状态和线程控制 §9.4 线程优先级和线程的调度 §9.5 线程同步 §9.6 线程的死锁. §9.1 线程的概念. 进程:一个执行中的程序,有自己独立的内存空间等系统资源。 线程:程序中单个顺序的流控制,单个程序中多个线程共享系统资源。 多线程编程:将任务分成多个并发的子任务。 Java 支持多线程。. §9.2 Java 线程的创建. 继承 Thread 类的方法 实现 Runnable 接口的方法 - PowerPoint PPT Presentation

Citation preview

第九章 多线程程序设计§9.1 线程的概念§9.2 Java 线程的创建§9.3 线程状态和线程控制§9.4 线程优先级和线程的调度§9.5 线程同步§9.6 线程的死锁

§9.1 线程的概念进程:一个执行中的程序,有自己独立的内存空间等系统资源。线程:程序中单个顺序的流控制,单个程序中多个线程共享系统资源。多线程编程:将任务分成多个并发的子任务。Java 支持多线程。

§9.2 Java 线程的创建继承 Thread 类的方法实现 Runnable 接口的方法

线程体 有 run 方法(一般是一个循环,需 要以线程方式运行的代

码)

1 继承 Thread 类的方法1) 通过实现 Thread 类的子类、并置换其中的 run ()

方法定义线程体,然后创建该子类的对象创建线程。如: public class Counter extends Thread{…public void run(){ // 定义线程的行为 }… }

2) 启动线程Counter threadCounter = new Counter();threadCounter.start();

2 实现 Runnable 接口的方法1) 通过在类中实现 Runnable 接口,并在该类中提供 run ()方法的实现。如:

public class Counter implements Runnable{public void run(){ // 定义线程的行为} }

2) 启动线程1) Counter C1 = new Counter();

new Thread(C1).start();2) Counter C1 = new Counter();

Thread t1 = new Thread(C1);t1.start();

3 Thread 类构造器和 Threadgroup类Thread 类构造器:Thread()Thread(Runnable)Thread(Threadgroup,Runnable)Thread(String)Thread(Threadgroup,String)Thread(Runnable,String)Thread(Threadgroup,Runnable,String)

Runnable: 类的实例,调用 Run ()方法的对象Threadgroup :新创建的线程所属的线程组String :表示新线程的名字

3 Thread 类构造器和 Threadgroup类 Threadgroup 类(线程组):所有的线程一定属于某个线程组。属于 Java.lang 包。用于进行统一管理。

显式: ThreadGroup myThreadGroup = new ThreadGroup(“my group of threads”)隐式:新创建的线程自动的属于创建该线程的线程所在的线程组。

例 线程概念例子class PrintThread extends Thread { private int sleepTime; public PrintThread( String name ) { super( name ); sleepTime = (int) ( Math.random() * 5000 ); System.out.println( "Name: " + getName() + "; sleep: " + sleepTime ); }

例 线程概念例子 public void run() { try { System.out.println( getName() + " going to sleep" ); Thread.sleep( sleepTime ); }catch ( InterruptedException exception ) { System.err.println( exception.toString() );} System.out.println( getName() + " done sleeping" ); }}

例 线程概念例子public class PrintThreadTest { public static void main( String args[] ) { PrintThread thread1 = new PrintThread( "thread1" ); PrintThread thread2 = new PrintThread( "thread2" ); PrintThread thread3 = new PrintThread( "thread3" ); PrintThread thread4 = new PrintThread( "thread4" ); System.out.println( "\nStarting threads" ); thread1.start(); thread2.start(); thread3.start(); thread4.start(); System.out.println( "Threads started\n" ); } }

§9.3 线程状态和线程控制

类 Thread 的常用方法控制和状态的关系例子

线程状态对线程进行各种控制Thread 类中的方法实现

改变 Runnable 状态Running 状态Not Runnable 状态Dead 状态New Thread 状态

1 类 Thread 的常用方法方法 简要说明

isAlive() 判断线程目前是否已被启动并且未被终止wait() 使当前线程处于等待状态notify() 将正在等待当前管程的线程唤醒Suspend()

暂停线程的执行resume() 被方法 Suspend ()暂停的线程继续执行start() 开始线程的执行run() 线程中真正执行的程序块stop() 结束线程的执行sleep() 让目前正在执行的线程小睡片刻yield() 自愿将执行的权利交给其它同优先级线程

1 类 Thread 的常用方法方法 简要说明

join() 暂停当前线程的执行,等待调用该方法的线程结束后在继续执行本线程

interrupt() 中断线程组中所有线程或当前线程currentThread() 返回当前执行线程的引用对象 setName( ) 设置线程的名字getName() 返回线程的名字activeCount() 返回当前线程所在线程组中活动线程的个数getThreadGroup() 返回当前线程所属的线程组名setDaemon() 设置当前线程为 Daemon 线程isDaemon() 测试线程是否为 Daemon 线程

1 类 Thread 的常用方法方法 简要说明

toString() 返回线程的字符串信息,包括线程的名字、优先级和线程组

enumerate() 把当前线程的线程组中的活动线程拷贝到线程数组中,包括子线程

checkAccess() 确定当前线程是否允许修改该线程

2 控制和状态的关系创建 可运行

运行中

死亡

其它阻塞互斥阻塞

等待阻塞

Start()

调度时间到

运行结束

Yield()

Wait() Notify()Interrupt()

Synchronized

Sleep() Join()

线程结束或时间到或 interupt()

获得互斥使用权

不可运行

3 例子—时钟import java.awt.Graphics;import java.util.*;import java.applet.Applet ;public class Clock extends Applet implements Runnable{Thread clockThread;public void start(){

if(clockThread == null){clockThread = new Thread(this,"Clock");clockThread.start();}}

public void run(){while(clockThread!=null){

repaint();

3 例子—时钟 try{ clockThread.sleep(1000);

}catch(InterruptedException e){ } } }public void paint(Graphics g){

Calendar now = new GregorianCalendar(); Date trialTime = new Date(); now.setTime(trialTime);

g.drawString(now.get(Calendar.HOUR_OF_DAY)+":" +now.get(Calendar.MINUTE)+":"+now.get(Calendar.SECOND),5,10);}public void stop(){

clockThread = null; } }

3 例子—时钟 <html><applet code = "Clock.class" width = 50

height = 40></applet></html>

§9.4 线程优先级和线程的调度线程的调度取决于线程的优先级。采用先占式调度,先占调度有分为:

独占方式分时方式

线程的优先级可以用 setPriority() 显式进行设置。getPriority()获得优先级。线程优先级用整数表示。从 1到 10 , Thread.MIN_PRIORITY(1), Thread.MAX_PRIORITY(10), Thread.NORM_PRIORITY(5)例

§9.4 线程优先级和线程的调度—画线import java.applet.Applet;import java.awt.Color;public class RaceApplet extends Applet implements Runnable{final static int NUMRUNNERS = 2;final static int SPACING = 20;Runner runners[] = new Runner[NUMRUNNERS];Thread updateThread = new Thread(this,"control");public void init(){for(int i=0;i<NUMRUNNERS;i++){

runners[i] = new Runner(); runners[i].setPriority(i+1);} }

public boolean mouseDown(java.awt.Event evt,int x,int y){

§9.4 线程优先级和线程的调度if(!updateThread.isAlive()) updateThread.start();for(int i=0;i<NUMRUNNERS;i++){if(!runners[i].isAlive()) runners[i].start();}return true; }

public void paint(java.awt.Graphics g){g.setColor(Color.lightGray);g.fillRect(0,0,400,500);g.setColor(Color.black);for(int i=0;i<NUMRUNNERS;i++){ int pri = runners[i].getPriority(); g.drawString(new Integer(pri).toString(),0,(i+1)*SPACING); }update(g); }

§9.4 线程优先级和线程的调度public void update(java.awt.Graphics g ){

for(int i=0;i<NUMRUNNERS;i++) g.drawLine(SPACING,(i+1)*SPACING,SPACING+(runners[i].tick)/1000,(i+1)*SPACING);

}public void run(){while(updateThread!=null){

repaint();try{ updateThread.sleep(100);}catch(InterruptedException e){ }

}}

§9.4 线程优先级和线程的调度public void stop(){

for(int i=0;i<NUMRUNNERS;i++) {if(runners[i].isAlive()){runners[i] = null;}if(updateThread.isAlive()){ updateThread = null;}}

}Class Runner extends Thread{

public int tick = 1;public void run(){while(tick<400000) tick++;}}

§9.5 线程同步 -- 数据的完整性因多线程并发而引起执行顺序的不确定性,执行的不确定性会产生执行结果的不确定性。在多个线程需要共享数据时通常会产生这种不确定性。对共享对象的访问必须同步 ,叫做条件变量 .Java语言允许通过监视器 ( 有的参考书称其为管程 ) 使用条件变量实现线程同步 .监视器阻止两个线程同时访问同一个条件变量 . 它如同锁一样作用在数据上 .线程 1 进入withdrawal 方法时 ,获得监视器 (加锁 ); 当线程1 的方法执行完毕返回时 ,释放监视器 ( 开锁 ), 线程 2 的 withdrawal 方能进入 .

withdrawal()线程 1

监视器 线程 2

§9.5 线程同步 -- 数据的完整性用 synchronized来标识的区域或方法即为监视器监视的部分。一个类或一个对象有一个监视器 , 如果一个程序内有两个方法使用 synchronized标志 ,则他们在一个监视器管理之下 .

一般情况下,只在方法的层次上使用关键区

read write监视器

线程 1 线程 2

§9.5 线程同步 -- 数据的完整性此处给出的例子演示两个线程在同步限制下工作的情况 .class Account{ static int balance=1000; // 为什么用 static? static int expense=0; public synchronized void withdrawl(int amount) { if (amount<=balance) { balance-=amount; expense+=amount;} else { System.out.println(“bounced: “+amount);} }}

§9.5 线程同步 -- 等待同步数据

生产者 消费者.

.共享对象write read可能出现的问题 :

•生产者比消费者快时 ,消费者会漏掉一些数据没有取到•消费者比生产者快时 ,消费者取相同的数据 .• notify() 和 wait () 方法用来协调读取的关系 .• notify() 和 wait ()都只能从同步方法中的调用 .

§9.5 线程同步 -- 等待同步数据notify 的作用是唤醒正在等待同一个监视器的线程 .wait 的作用是让当前线程等待read() 方法在读信息之前先等待 ,直到信息可读 ,读完后通知要写的线程 .write() 方法在写信息之前先等待 ,直到信息被取走 ,写完后通知要读的进程 .

例 生产者 /消费者例子(有同步)// HoldIntegerSynchronized.javapublic class HoldIntegerSynchronized { private int sharedInt = -1; private boolean writeable = true; // condition variable public synchronized void setSharedInt( int val ) { while ( !writeable ) { // not the producer's turn try { wait(); } catch ( InterruptedException e ) { e.printStackTrace(); } } System.err.println( Thread.currentThread().getName() + " setting sharedInt to " + val );

sharedInt = val; writeable = false; notify(); // tell a waiting thread to become ready }public synchronized int getSharedInt() { while ( writeable ) { // not the consumer's turn try { wait(); } catch ( InterruptedException e ) { e.printStackTrace(); } } writeable = true; notify(); // tell a waiting thread to become ready

System.err.println( Thread.currentThread().getName() + " retrieving sharedInt value " + sharedInt ); return sharedInt; }}// ProduceInteger.javapublic class ProduceInteger extends Thread { private HoldIntegerSynchronized pHold; public ProduceInteger( HoldIntegerSynchronized h ) { super( "ProduceInteger" ); pHold = h; } public void run() { for ( int count = 1; count <= 10; count++ ) { pHold.setSharedInt( count ); }

System.err.println( getName() + " finished producing values" + "\nTerminating " + getName() ); }}// ConsumeInteger.javapublic class ConsumeInteger extends Thread { private HoldIntegerSynchronized cHold; public ConsumeInteger( HoldIntegerSynchronized h ) { super( "ConsumeInteger" ); cHold = h; } public void run() { int val, sum = 0; do { val = cHold.getSharedInt(); sum += val;

} while ( val != 10 ); System.err.println(getName() + " retrieved values totaling: " + sum + "\nTerminating " + getName() ); }}// SharedCell.javapublic class SharedCell { public static void main( String args[] ) { HoldIntegerSynchronized h = new HoldIntegerSynchronized(); ProduceInteger p = new ProduceInteger( h ); ConsumeInteger c = new ConsumeInteger( h ); p.start(); c.start(); } }

§9.6 线程的死锁死锁:指两个或多个线程无止境地相互等待的过程。错误的同步往往会引起死锁。程序员认真设计避免死锁。如果你持有一个锁并试图获取另一个锁时 ,就有死锁的危险 .解决死锁问题的方法 : 对共享资源访问的顺序 ,即给条件变量施加排序。

线程 2

pen

线程 1

note

把“ pen” 给我 ,我才能给你“ note”

把“ note” 给我 ,我才能给你“ pen”

§9.7 多线程问题 --- 线程间的通信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

§9.7 多线程问题 --- 线程间的通信管道流不能直 接读写

PrintStream p = new PrintStream( pos );p.println(“hello”);DataInputStream d=new DataInputStream(pis);d.readLine();

管道流可以连接两个线程间的通信将一个写线程的输出通过管道流定义为读线程的输入 .

printStream DataInputStream

§9.7多线程问题 -- 线程间的通信主类 Pipethread

辅类Writer线程类

辅类Reader线程类

管道流将数据写到输出流

从流中读数据

输入流

作为参数传给 WriterWriter( outStream )

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 多线程问题 --- 线程间的通信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 多线程问题 -- 线程间的通信.

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;

}}

小结线程概念线程状态及线程控制线程优先级和线程调度线程同步线程通信读写程序

习题1. 什么是线程?线程和进程的区别。2. 创建线程有哪些方法,如何使用?3. 线程有哪几种状态?是如何进行控制的?4. 请编写一个程序,实现在一分钟后显示当时的时间。

5. 当我们编译下面的代码时,会发生什么情况?

习题Public class Runt implements Runnable{ public static void main(String args[]){ Runt r1 = new Runt(); Thread t = new Thread(r1); t.start();} public void start(){ for(int i = 0;i<100;i++) System.out.println(i); } }

习题6. 创建两个线程的实例 , 分别将一个数组从小到大和从大到小排列 .输出结果 .7. 当我们编译运行下面的代码时,会发生什么情况?Public class TGo implements Runnable{ public static void main(String args[]){ TGo r1 = new TGo(); Thread t = new Thread(r1); t.start();} public void run(){ while(true){ Thread.currentThread().sleep(1000); System.out.println(" looping while "); } }

public class ThreadTest { public static void main( String args[] ) { int a[] = { 2, 6, 4, 8, 10, 12, 89, 68, 45, 37 }; for (int i=0;i<a.length;i++) System.out.print(a[i]+" "); System.out.println(); SortAsc threadA = new SortAsc( a ); SortDes threadD = new SortDes ( a ); threadA.start(); threadD.start(); } }

class SortAsc extends Thread { int sort[]; public SortAsc( int temp[]) {sort = new int[temp.length]; System.arraycopy( temp,0,sort,0,temp.length); } public void run() {for ( int pass = 1; pass < sort.length; pass++ ) for ( int i = 0; i < sort.length - 1; i++ ) if ( sort[ i ] >sort[ i + 1 ] ) swap( sort, i, i + 1 ); for (int i=0;i<sort.length;i++) System.out.print(sort[i]+" "); System.out.println(); } public void swap( int c[], int first, int second ){ int hold; hold = c[ first ]; c[ first ] = c[ second ]; c[ second ] = hold; } }

class SortDes extends Thread { int sort[]; public SortDes ( int temp[] ) {sort = new int[temp.length]; System.arraycopy( temp,0,sort,0,temp.length); } public void run() {for ( int pass = 1; pass < sort.length; pass++ ) for ( int i = 0; i < sort.length - 1; i++ ) if ( sort[ i ] <sort[ i + 1 ] ) swap( sort, i, i + 1 ); for (int i=0;i<sort.length;i++) System.out.print(sort[i]+" "); System.out.println(); } public void swap( int c[], int first, int second ){ int hold; hold = c[ first ]; c[ first ] = c[ second ]; c[ second ] = hold; } }