第7章 Java 的多线程机制
本章学习要点 1.理解线程的概念和Java的多线程机制 2.理解线程生命周期中的几种状态及状态间的转换 3.了解线程的优先级及线程调度方法 4.掌握实现多线程的两种方法 —继承Thread类和实现Runnable接口 5.掌握线程的其他常用方法 —sleep()、isAlive()、yield()、interrupt()等的用法 6.掌握线程的同步和联合的实现
目录 CONTENTS 线程的概念和Java的多线程机制 7.1 7.2 线程的状态与生命周期 线程的优先级及线程的调度 7.3 7.4 实现多线程的两种方法 线程的优先级及线程的调度 7.2 7.5 线程的常用方法 7.6 线程的同步 7.7 线程的联合
7.1 线程的概念和Java的多线程机制
多线程是Java的特点之一,掌握多线程编程技术,可以充分利用CPU的资源,更容易解决实际中的问题。 多线程作为一种多任务并发的工作方式,在实际应用中,广泛应用于和网络有关的程序设计中,可以建立清晰的模型,表现出较大优势。因此,理解Java的多线程机制,对于Java面向对象程序设计的学习至关重要。
7.1.1 操作系统与进程 程序是一段静态的代码,而进程则是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展直至消亡的过程。 操作系统可以同时管理计算机系统中的多个进程,让多个进程轮流使用CPU资源,甚至可以让多个进程共享操作系统所管理的资源,比如让Word进程和其他的文本编辑器进程共享系统的剪贴板。 进程1 进程2 进程3 操作系统
7.1.2 进程与线程 公共数据区 线 程 1 线程2 线程3 线程4 获得CPU时间 空闲时间 进程 线程是比进程更小的执行单位,一个进程在其执行过程中,可以产生多个线程,形成多条执行线索。每条线索,即每个线程也有它自身的产生、存在和消亡的过程。 1 2 线程间可以共享进程中的某些内存单元(包括代码与数据),并利用这些共享单元来实现数据交换、实时通信与必要的同步操作。
7.1.3 Java的多线程机制 1 Java语言的一大特点就是内置对多线程的支持。多线程是指一个应用程序中同时存在几个执行体,按几条不同的执行线索共同工作的情况,它使得编程人员可以很方便地开发出能同时处理多个任务的功能强大的应用程序。 2 Java程序单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程(多条执行线索),每个线程执行不同的任务。 3 多线程的程序在运行时,Java虚拟机快速地把控制从一个线程切换到另一个线程。这些线程将被轮流执行,使得每个线程都有机会使用CPU资源。
7.1.4 主线程(main线程) 一 每个Java应用程序都有一个缺省的主线程。我们已经知道,Java应用程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法后,就会启动一个线程,这个线程称为主线程(main线程),该线程负责执行main方法。 二 在main方法的执行中再创建的线程,就称为程序中的子线程。如果main方法中没有创建子线程,那么当main方法执行完最后一个语句,即 main方法返回时,JVM就会结束我们的Java应用程序。
如果main方法中创建了子线程,那么JVM就要在主线程和子线程之间轮流切换,保证每个线程都有机会使用CPU资源。 main方法即使执行完最后的语句,JVM也不会结束Java应用程序,JVM一直要等到Java应用程序中的所有线程都结束之后,才结束Java应用程序。 创建并启动一个新线程thread1 创建并启动一个新线程thread2 主线程结束 thread1线程结束 thread2线程结束 执行期 主线程 main 多线程的执行流程
7.1.5 多线程的优势 优势 提高计算机系统CPU的利用率。 提高应用程序的响应。 改善程序结构。 7.1.5 多线程的优势 多线程作为一种多任务并发的工作方式,在实际应用中具有以下优势: 提高计算机系统CPU的利用率。 多线程可以充分利用现代计算机的多CPU或单CPU运算速度快的特点,从而节省响应时间。 优势 提高应用程序的响应。 尤其是在图形界面程序中,通过多线程技术,将耗时长的操作放置到一个新线程中执行,而界面仍能正常响应用户的操作,这样可以增强用户体验。 改善程序结构。 一个功能复杂的程序(进程)可以被划分为多个线程,每个线程完成相对独立的功能,使得程序结构清晰、利于理解和修改。
之前我们所接触的程序(以Java Application为例): public class mainClass { public static void main(String args[]) { 第一条语句; 第二条语句; ….. 最后一条语句; } main()方法是程序执行的入口点,按语句的顺序,逐条执行,执行完最后一条语句,程序执行完毕。 以上程序只有一条执行线索,程序的语句是串行的。程序运行过程中(进程),只包含一个主线程,不存在其它的子线程。
思考:以下程序的第二个循环可以被执行吗? public class mainClass { public static void main(String args[]) { } while(true) { System.out.println(“Hello!”); } while(true) { System.out.println(“大家好!”); } 以上程序只有一个主线程存在,但:如果建立两个子线程,让每个子线程分别执行一个无限循环,则程序结构清晰,且第二个循环有机会被执行
修改为多线程的程序 public class mainClass { public static void main(String args[]) { //创建子线程1,让其执行第1个无限循环! //创建子线程2,让其执行第2个无限循环! } Thread1 th1=new Thread1(); th1.start(); Thread2 th2=new Thread2(); th2.start(); 以上程序在运行时(进程)包含几个线程呢? 3 个:1个主线程,2个子线程
class Thread1 extends Thread { public void run() while(true) { System.out.println(" Hello!"); } }
class Thread2 extends Thread { public void run() while(true) { System.out.println(“ 大家好!"); } }
7.2 线程的状态与生命周期
线程创建后,就开始了它的生命周期,在不同的生命周期阶段线程有不同的状态。对线程调用各种控制方法,就使线程从一种状态转换为另一种状态。 可运行状态 阻塞状态 线程的生命周期主要分为如下几个状态 新建状态 运行状态 死亡状态
7.2.1 新建状态(New) 新建状态 调用一个线程类的构造方法,便创建了一个线程,如: Thread t1=new Thread(); 此时线程处于新建状态。处于新建状态的线程还没有被分配有关的系统资源。
7.2.2 可运行状态(Runnable) 处于新建状态的线程调用start(),使线程的状态转换为可运行状态。start()方 法使系统为线程分配必要的资源,并将线程交给系统调度。 此时线程仅仅是可以运行,但不一定在运行中。在多线程程序设计中,系统中往往会有多个线程同时处于Runnable状态,它们将竞争有限的CPU资源,由JVM根据线程调度策略进行调度。
7.2.3 运行状态(Running) 运行状态是线程占有CPU并实际运行的状态。此时线程状态的变迁有如下三种情况: 如果线程正常执行结束或应用程序停止运行,线程将进入死亡状态。 如果当前线程执行了yield()方法,或者当前线程因调度策略(执行过程中,有一个更高优先级的线程进入可运行状态,这个线程立即被调度执行,当前线程占有的CPU被抢占;或在分时方式时,当前执行线程执行完时间片)由系统控制进入可运行状态。 如果发生下面几种情况时,线程就进入阻塞状态 线程调用了sleep()方法或join()方法,进入阻塞状态 线程调用wait()方法时,由运行状态进入阻塞状态 如果线程中使用synchronized来请求对象锁但未获得时,进入阻塞状态
阻塞状态 7.2.4 阻塞状态(Blocked) 阻塞状态根据产生的原因又可分为对象锁阻塞、等待阻塞和其他阻塞。状态相应变迁如下: 线程调用wait()方法时,线程由运行状态进入等待阻塞状态。在等待阻塞状态下的线程若被notify()或notifyAll()唤醒,则进入可运行状态。 线程调用了sleep()方法或join()方法时,线程进入其他阻塞状态。由于调用sleep()方法而进入其他阻塞状态的线程,睡眠时间到将进入可运行状态;由于调用t.join()方法而进入其他阻塞状态的线程,当t线程执行结束时,进入可运行状态。 阻塞状态 如果线程中使用synchronized来请求对象的锁但未获得时,进入对象锁阻塞状态,该状态下的线程如果获得对象锁,将 进入可运行状态。
1 3 2 7.2.5 死亡状态(Dead) 一个线程对象在以下三种情况下,会结束并且进入死亡状态: 强制退出:调用线程的实例方法stop()可以强制停止当前线程。由于stop()方法会导致死锁,目前该方法已过时,不推荐使用。 2 线程执行时遇到一个未捕获的异常,线程被终止并且进入死亡状态。 3 自然撤消:是指从线程的run()方法执行完毕后正常退出。 1
线程生命周期中的状态变迁示意图 新建 可运行 运行 死亡 其它阻塞 对象锁阻塞 等待阻塞 sleep()时间到 start() sleep()或join() run()方法结束 wait() 获得锁 Synchronized notify() notifyAll() scheduler() yield()
7.3 线程的优先级及线程的调度
线程优先级的概念 同一时刻如果有多个线程处于可运行状态,则它们需要排队等待CPU资源。此时每个线程自动获得一个线程的优先级,优先级的高低反映线程的重要或紧急程度。 1 可运行状态的线程按优先级排队,线程调度采用优先级调度策略,优先级高的线程应该有更大的获取CPU资源执行的概率。 2 当前正在执行的线程,其优先级一般不会比处于等待状态的线程优先级低。 3
线程优先级的设置 Java中线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认优先级为5。 当需要改变线程的优先级时,可以通过调用setPriority()方法来实现,下面为该方法的声明: public final void setPriority(int newPriority) 参数newPriority表示需要设置的优先级别,应该是1-10之间的整数。 为了便于记忆,Java中也提供了3个常量来表示比较常用的优先级别,分别为MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY,分别代表数值10,5,1。
有关线程优先级的程序示例 线程类的子类— Thread1类 线程类的子类—Thread2类 class Thread1 extends Thread { public void run() for(int i=1;i<=10000;i++) { System.out.print("<Min"+i+">"); } class Thread2 extends Thread { public void run() for(int i=1;i<=10000;i++) { System.out.print("<Max"+i+">"); }
主类(测试类)—testThread public class testThread { public static void main(String args[]) { //创建两个线程对象 Thread1 t1=new Thread1(); Thread2 t2=new Thread2(); //设置两个线程的优先级 t1.setPriority(Thread.MIN_PRIORITY); t2.setPriority(Thread.MAX_PRIORITY); //启动两个线程 t1.start(); t2.start(); } }
程序运行结果及分析 程序运行结果: 结果分析: (1)程序中为两个线程对象t1、t2分别设置为最低、最高优先级,在启动时,虽然先启动了优先级低的t1线程,但最终还是优先级高的t2线程首先运行完毕,然后优先级低的线程t1才运行完毕。(2)说明了“优先级高的线程有更大的获取CPU资源执行的概率”
7.4 实现多线程的两种方法
7.4.1 Thread类—实现多线程的基础类 创建线程需要通过调用Thread类的构造方法来实现。 Thread类的常用构造方法: (1) public Thread() (2) public Thread(Runnable target) (3) public Thread(String name) (4) public Thread(Runnable target , String name) name:指定了线程的名称 target: 实现Runnable接口的对象,也就是提供线程体的对象。 java.lang.Runnable接口中定义了run()方法,实现该接口的类需要通过实现run()方法来提供线程体,当线程运行时,run()方法将被系统自动调用。
7.4.2 Runnable接口—提供了线程的具体实现 线程创建中,线程体的构造是关键。任何实现Runnable接口的对象都可以作为Thread类构造方法中的target参数,而Thread类本身也实现了Runnable接口。 因此可以有两种方式提供run() 方法的实现--也就是创建线程体 (1)实现Runnable接口 ; (2)继承Thread类
7.4.3 通过实现Runnable接口实现多线程 在java.lang包中Runnable接口的定义为: public interface Runnable { public void run(); } 使用这种方式创建线程的步骤如下: 首先定义一个类,让其实现Runnable接口,在该类中提供run()方法的实现 步骤一 然后把该类的一个实例作为参数传递给Thread类的构造方法,来创建Thread对象 步骤二
例: 通过实现Runnable接口实现多线程 class Hello implements Runnable { int i; public void run() { while(true) { System.out.println("Hello"+i++); if(i==5) break; } Hello类实现了Runnable接口,并重写其中的run()方法。 在主类的main()方法中,创建线程对象t1,该线程对象的线程体是由Hello类的对象来提供的。 新建的线程t1不会自动运行,必须调用start()方法,才让其可被调度运行。 t1运行后,执行的就是Hello对象中的run()方法。 public class ThreadTest { public static void main(String args[]) { Thread t1=new Thread(new Hello()); t1.start(); }
7.4.4 通过继承Thread类实现多线程 在java.lang包中,Thread类的声明如下: public class Thread extends Object implements Runnable Thread类本身已经实现了Runnable接口。 通过继承Thread类创建线程的步骤如下: 首先从Thread类派生子类,并通过重写其中的run()方法来定义线程体。 步骤一 然后通过创建该子类的对象来创建线程。 步骤二
例: 通过继承Thread类实现多线程 class Hello extends Thread { int i; public void run() { while(true) { System.out.println("Hello"+i++); if(i==5) break; } } } 类Hello继承了Thread类,并对run()方法进行了重写。 public class ThreadTest2 { public static void main(String args[]) { Hello t1=new Hello(); t1.start(); } } 在主类的main()方法中,创建Hello的实例t1,并调用其start()方法,让其可被调度运行。线程对象t1运行后,执行的就是run()方法。
实现多线程的两种方法的比较——采用继承Thread类的方法 优势 编写简单,在run()方法中如果需要调用当前线程的其他方法,只需使用this即可,无需使用Thread.currentThread()方法。 劣势 因为这种线程类已经继承了Thread类,所以不能再继承其它类。
实现多线程的两种方法的比较——实现Runnable接口的方法 优势 线程类只是实现了Runnable接口,因此还可以继承其他类;在这种情况下,可以使多个线程共享一个target对象,所以非常适合多个线程用来处理同一份资源的情况,模型清晰,更加符合面向对象的设计思想。 劣势 编程略有些复杂,如果要访问当前线程必须使用Thread.currentThread()方法。如:想获得当前活动线程的名称,用语句 : Thread.currentThread().getName() 提倡采用通过实现Runnable接口的方式来实现多线程。 具体应用中,可以根据实际情况确定采用哪种方法。
上节课内容回顾 (1) 实现Runnable接口 (2) 继承Thread类 都是通过重写run()方法来构造线程体 程序 、 进程、 线程的区别 Java程序中把单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务,线程间可以共享进程中的内存单元(包括代码和数据)。 Java语言的一大特点是内在支持多线程,每个java应用程序都有一个缺省的主线程,负责执行main()方法,mian()方法中可以创建其它线程。 线程是抢占式的,默认是正常优先级5,可用setPriority()改变优先级,让高优先级线程优先使用CPU。
7.5 线程的常用方法
除了前面介绍的方法外,线程还包含一些其它常用的方法,如sleep()、isAlive()、yield()、interrupt()方法。 对这些方法的合理应用,能够很好地模拟并解决实际问题。 以下分别介绍这些方法。
(1) sleep( ) —线程的休眠方法 sleep()方法强制当前正在执行的线程,让其休眠(暂停执行),当休眠时间到期,则返回到可运行状态。 sleep()方法的格式是: Thread.sleep(long millis):休眠时间以毫秒为单位 对sleep()的调用一般是在线程体的run()方法内,同时应捕获中断异常。 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
public class TestSleep extends Thread { public void run() { for (int i = 0; i < 20; i++) { if (i % 10 == 0) { System.out.println("-------" + i); } System.out.print(i); try { Thread.sleep(1000); System.out.print(" 线程睡眠1秒!\n"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { new TestSleep().start(); //创建线程并启动 }
(2) isAlive() —判断线程是否启动的方法 如果线程已被启动并且未被终止,那么isAlive()返回true; 如果线程处于新建或终止状态,那么isAlive()返回false。 在实际应用中,可以使用isAlive()方法判断指定线程是否执行完毕。 if ( t.isAlive() ) { //相应的处理语句 }
(3) currentThread( ) —获取当前活动线程的方法 currentThread()方法是Thread类的static方法, 返回正在运行的线程。 在多个线程共享线程体的情况下,在线程体的run()方法中,用以下语句: Thread.currentThread() :返回当前运行的线程对象 Thread.currentThread().getName():返回当前运行的线程对象的名称 (4) yield() — 线程的让步方法 yield()方法会暂停当前正在执行的线程对象,把执行机会让给具有相同或更高优先级的其它线程。 当其它线程执行完毕后,该线程继续执行。
yield()方法使用示例 public class ThreadYieldDemo{ public static void main(String[] args) { System.out.println(“当前的线程是”+ Thread.currentThread().getName()); Thread thread1=new Thread(new YieldThread()); thread1.start(); Thread thread2=new Thread(new YieldThread()); thread2.start(); }
class YieldThread implements Runnable{ public void run() { for(int i=1;i<=10;i++) { System.out.println(Thread.currentThread().getName()+ "运行了"+i+"次"); if(i==5) //当i=5时,当前正在执行的线程让步给其它线程 { Thread.currentThread().yield(); }
在本程序中的线程体run()方法中,当i=5时,暂停当前正在执行的子线程,使其让步于其它的子线程执行。
(5) interrupt() —线程的中断方法 interrupt()方法用来吵醒休眠的线程。当线程调用sleep()方法处于休眠状态时,一个正在运行的线程可以调用处于休眠状态线程的interrupt()方法,使其被 “吵醒”,导致其发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源。 在下面的例子中 ,有两个线程:student和teacher,分别模拟学生和教师。其中student准备睡一小时后再开始上课,teacher在讲了3句“上课”后,吵醒睡眠的线程student,使其开始听课。
interrupt()方法示例 class ClassRoom implements Runnable //该类用来模拟教室 { Thread student,teacher; //模拟学生和教师 ClassRoom() teacher=new Thread(this); //创建teacher线程 student=new Thread(this); //创建student线程 teacher.setName(“纪老师”); //设置线程的名称 student.setName("张三"); }
public void run(){ if (Thread.currentThread()==student) { try{ System.out.println(student.getName()+"正在睡觉,不听课"); Thread.sleep(1000*60*60); //休眠1小时 } catch(InterruptedException e) { System.out.println(student.getName()+"被老师叫醒了"); System.out.println(student.getName()+"开始听课");
else if(Thread.currentThread()==teacher){ for(int i=1;i<=3;i++){ System.out.println("上课!"); try {Thread.sleep(500);} catch(InterruptedException e) {} } student.interrupt(); //吵醒(中断)student
public class TestInterrupt //主类 { public static void main(String args[]) ClassRoom room=new ClassRoom(); room.student.start(); room.teacher.start(); }
7.6 线程的同步
为什么要同步?什么是线程同步? 当多个线程同时访问同一个变量, 特别是一些线程需要修改这个变量时,程序应对这样的问题做出处理, 否则可能导致数据的不一致。 如: 一个工资管理负责人正在修改雇员的工资表, 而一些雇员也正在领取工资,如果允许这样做必然出现混乱。因此, 工资管理负责人正在修改工资表时, 将不允许任何雇员领取工资, 也就是说这些雇员必须等待。 所谓线程同步就是程序中的若干个线程都需要使用一个方法, 而这个方法用synchronized给予了修饰。多个线程调用synchronized方法必须遵守同步机制。
线程同步机制 在Java中,当一个线程A使用synchronized方法时, 其他线程想使用这个synchronized方法时就必须等待, 直到线程A使用完该synchronized 方法。 在使用多线程解决许多实际问题时, 需要把某些修改数据的方法用关键字synchronized来修饰, 即使用同步机制, 来保证数据的一致性。
线程同步的程序示例 本例中有两个线程:会计和出纳 他们共同拥有一个帐本。都可以使用saveOrTake(int amount)方法对帐本进行访问: 会计使用saveOrTake(int amount)方法时, 向帐本上写入存钱记录; 出纳使用saveOrTake(int amount)方法时,向帐本写入取钱记录。因此,当会计正在使用saveOrTake(int amount)时,出纳被禁止使用,反之也是这样。 程序要保证其中一个线程使用saveOrTake(int amount)时, 另 一个线程将必须等待,即saveOrTake(int amount) 方法应当是一个synchronized方法。
程序运行界面
ThreadSynchronization.java public class ThreadSynchronization { public static void main(String args[]) { Bank bank=new Bank(); bank.setMoney(200); System.out.println("目前帐上有"+bank.money+"万"); Thread accountant,cashier; //会计,出纳两个线程 accountant=new Thread(bank); cashier=new Thread(bank); accountant.setName("会计"); cashier.setName("出纳"); accountant.start(); cashier.start(); } }
class Bank implements Runnable { int money=200; public void setMoney(int n) { money=n; } public void run() { if(Thread.currentThread().getName().equals("会计")) saveOrTake(300); else if(Thread.currentThread().getName().equals("出纳")) saveOrTake(150);
public synchronized void saveOrTake(int amount) { //存取方法 if(Thread.currentThread().getName().equals("会计")){ for(int i=1;i<=3;i++){ money=money+amount/3; //每存入amount/3,稍歇一下 System.out.println(Thread.currentThread().getName()+ "存入"+amount/3+",帐上有"+money+"万,休息一会再存"); try {Thread.sleep(1000); //这时出纳仍不能使用saveOrTake方法 } catch(InterruptedException e) {}
else if(Thread.currentThread().getName().equals("出纳")){ for(int i=1;i<=3;i++){ money=money-amount/3; //每取出amount/3,稍歇一下 System.out.println(Thread.currentThread().getName()+"取出"+amount/3+",帐上有"+money+"万,休息一会再取"); try {Thread.sleep(1000); //这时会计仍不能使用saveOrTake方法 } catch(InterruptedException e) {}
思考:如果去掉saveOrTake方法的同步修饰符synchronized, 程序的运行结果会是怎样? 如上图所示, 去掉saveOrTake方法的synchronized修饰符, 两个线程对于共享变量money的并发存取, 造成了数据的不一致。 因此, 如果多个线程并发地访问同一个变量, 则该问该变量的方法头中, synchronized修饰符一定要加上。
通过wait()、notify()/notifyAll()方法来协调同步的线程 wait方法可以中断线程的执行,使本线程等待,暂时让出CPU的使用权,并允许其他线程使用这个同步方法。 其他线程在使用完这个同步方法的同时,应当用notifyAll()方法通知所有由于使用这个同步方法而处于等待状态的线程结束等待,从中断处继续执行这个同步方法。如果使用notify()方法,那么只是通知处于等待中的线程的某一个结束等待状态。
wait()、notify()和notifyAll()都是Object类中的final方法 这些方法只有在被synchronized修饰的方法中才能使用。
编程模型:(1)张三--线程1:买票时,因无法找零,必须wait(),让李四先买票; 通过wait()、notifyAll()来协调线程同步的示例 问题描述: 在以下的程序中,我们来模拟张三和李四两个人买电影票的事件。 售票员只有两张5元钱,电影票5元钱一张。 张三拿20元一张的人民币排在李四的前面买票,李四拿一张5元的人民币买票。 由于张三买票时,售票员无法找零,所以张三要让李四先买票。等李四买好票后,张三才能开始买票。 编程模型:(1)张三--线程1:买票时,因无法找零,必须wait(),让李四先买票; (2)李四--线程2:买票成功后,notify()张三,张三才能买票。
WaitandNotifyDemo.java public class WaitandNotifyDemo { public static void main(String args[]) TicketSaler saler=new TicketSaler(); Thread zhangsan,lisi; //两个线程对象 zhangsan=new Thread(saler); zhangsan.setName("张三"); lisi=new Thread(saler); lisi.setName("李四"); zhangsan.start(); lisi.start(); }
class TicketSaler implements Runnable { int fiveAmount=2,tenAmount=0,twentyAmount=0; public void run() if(Thread.currentThread().getName().equals("张三")){ saleTicket(20); } else if(Thread.currentThread().getName().equals("李四")){ saleTicket(5);
private synchronized void saleTicket(int money) { //如果使用该方法的线程传递的参数是5,就不用等待 if(money==5) fiveAmount=fiveAmount+1; System.out.println("给" + Thread.currentThread().getName() +"入场券,"+Thread.currentThread().getName()+ "的钱正好"); notifyAll(); }
else if(money==20){ while(fiveAmount<3) { try { System.out.println("\n"+Thread.currentThread().getName() +"靠边等..."); wait(); +"继续买票"); } catch(InterruptedException e) {}
fiveAmount=fiveAmount-3; twentyAmount=twentyAmount+1; System.out.println("给" +Thread.currentThread().getName()+"入场券,"+ Thread.currentThread().getName()+ "给20,找回15元"); }
程序运行界面
7.7 线程的联合
什么是线程的联合(join)? 1 有时需要通过线程之间的接力来完成某项任务,这时需要调用线程类的join()方法。join()方法可以使两个交叉执行的线程变成顺序执行。 2 一个线程A在占有CPU资源期间,可以调用其他线程的join()方法,从而让其他线程和本线程联合,如:B. join();我们称线程A在运行期间联合了线程B。 3 线程A一旦联合了线程B,那么线程A将立刻中断执行,一直等到线程B执行完毕,线程A再从中断处恢复执行。
程序示例: 以下程序通过使用线程的联合来模拟顾客等待蛋糕师制作蛋糕的事件。 程序涉及两个子线程: (1)customer线程,模拟顾客; (2)cakeMaker线程,模拟蛋糕师。 顾客(customer线程)到蛋糕店中为了完成取蛋糕的任务,必须要联合蛋糕师(cakeMaker线程)---只有等待蛋糕师把蛋糕制作完毕,然后才能取走蛋糕,从而完成取蛋糕的任务。 customer线程要联合cakeMaker线程: 调用:cakeMaker.join()
public class ThreadJoinDemo { public static void main(String args[]) ThreadJoinDemo.java public class ThreadJoinDemo { public static void main(String args[]) ThreadJoin a=new ThreadJoin(); Thread customer=new Thread(a); Thread cakeMaker=new Thread(a); customer.setName("顾客"); cakeMaker.setName("蛋糕师"); a.setJoinThread(cakeMaker); customer.start(); }
class ThreadJoin implements Runnable{ Cake cake; Thread joinThread; public void setJoinThread(Thread t) { joinThread=t; } public void run(){ if(Thread.currentThread().getName().equals("顾客")){ System.out.println(Thread.currentThread().getName() +"等待"+joinThread.getName()+"制作生日蛋糕"); try { joinThread.start(); joinThread.join(); } //当前线程开始等待joinThread结束 catch(InterruptedException e) {} +"买了"+cake.name+",价格:"+cake.price); }
else if(Thread.currentThread()==joinThread) { System.out.println(joinThread.getName()+ "开始制作生日蛋糕,请等待......"); try { Thread.sleep(2000); } //休眠2秒 catch(InterruptedException e) {} cake=new Cake("生日蛋糕",288); System.out.println(joinThread.getName()+ "已把蛋糕制作完毕"); }
class Cake //内部类 { int price; String name; Cake(String name,int price) this.name=name; this.price=price; }
本 章 小 结 线程的概念和Java的多线程机制 线程的状态与 生命周期 线程的优先级及线程的调度 本 章 小 结 线程的概念和Java的多线程机制 线程的概念、主线程(main)和子线程的概念、多线程的优势 线程的状态与 生命周期 线程的新建、可运行、运行、阻塞、死亡5种状态及状态间的转换 线程的优先级及线程的调度 优先级的含义、线程优先级的设置方法及线程调度的基本原则
本 章 小 结(续) 实现多线程的 两种方法 线程的常用方法 线程的同步和联合 学习要求: 本 章 小 结(续) 学习要求: --掌握线程的相关概念和Java的多线程机制 --掌握实现多线程的两种方法并使用线程的常用方法来控制线程 --掌握线程同步和联合的实现 --本章程序示例中的核心代码要加强练习、熟练掌握 实现多线程的 两种方法 extends Thread、implements runnable; 重点掌握线程体的构造,即run()方法的编写; 两种实现方法的优势和劣势。 线程的常用方法 sleep():线程休眠;isAlive():判断线程是否启动; currentThread():获取当前活动线程; yield():线程让步;interrupt():线程中断 ------注意理解方法的含义及用法------ 线程的同步和联合 利用synchronized实现线程同步,解决线程的并发引起的数据不一致问题 通过wait、notify/notifyAll来协调线程的同步 通过join方法联合其它的线程
谢谢观看!