1、第第16章章 多线程多线程本章要点本章要点u 了解多线程在了解多线程在Windows系统的执行模式系统的执行模式u 掌握实现线程的两种方式掌握实现线程的两种方式u 掌握线程的状态掌握线程的状态u 掌握使线程进入各种状态的方法掌握使线程进入各种状态的方法u 掌握线程的优先级掌握线程的优先级u 掌握线程安全掌握线程安全u 掌握线程同步机制掌握线程同步机制u 掌握线程间的通信掌握线程间的通信第第16章章 多线程多线程主要内容主要内容16.1 线程简介线程简介n世间万物会同时完成很多工作:世间万物会同时完成很多工作:例如人体同时进行呼吸、血液循环、思例如人体同时进行呼吸、血液循环、思考问题等活动;考问
2、题等活动;用户既可以使用计算机听歌,也可以使用户既可以使用计算机听歌,也可以使用它打印文件,用它打印文件,n而这些活动完全可以而这些活动完全可以同时进行同时进行,这种思想在,这种思想在Java中被称为中被称为并发并发,而将并发完成的每一,而将并发完成的每一件事情称为件事情称为线程线程。16.1 线程简介线程简介n在人们的生活中,并发机制非常重要,在人们的生活中,并发机制非常重要,但是但是并不是所有的程序语言都支持线程并不是所有的程序语言都支持线程。n在以往的程序中,多以一个任务完成后再进在以往的程序中,多以一个任务完成后再进行下一个项目的模式进行开发,这样下一个行下一个项目的模式进行开发,这样
3、下一个任务的开始必须等待前一个任务的结束。任务的开始必须等待前一个任务的结束。nJava语言提供语言提供并发机制并发机制,程序员可以在程序,程序员可以在程序中执行中执行多个线程多个线程,每一个线程完成,每一个线程完成一个功能一个功能,并与其他线程并与其他线程并发执行并发执行,这种机制被称为,这种机制被称为多多线程线程。16.1 线程简介线程简介nJava中的多线程在每个操作系统中的运行中的多线程在每个操作系统中的运行方式也存在差异,在此着重说明多线程在方式也存在差异,在此着重说明多线程在Windows操作系统操作系统的运行模式。的运行模式。nWindows操作系统是多任务操作系统,它操作系统是
4、多任务操作系统,它以进程为单位。以进程为单位。n一个进程是一个包含有自身地址的程序,一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程,也就是每个独立执行的程序都称为进程,也就是正在执行的程序。正在执行的程序。16.1 线程简介线程简介n进程进程是一个用来描述处于动态运行状态的应是一个用来描述处于动态运行状态的应用程序的概念,即一个进程就是用程序的概念,即一个进程就是一个执行中一个执行中的程序的程序,每个进程都有一块自己,每个进程都有一块自己独立的地址独立的地址空间空间,并可以包含,并可以包含多个线程多个线程。这些线程将。这些线程将共共享享进程的地址空间及操作系统分配给这个进进程
5、的地址空间及操作系统分配给这个进程的资源。程的资源。n线程线程一般是指进程中的一般是指进程中的一个执行流一个执行流,多线程,多线程是指在一个进程中是指在一个进程中同时运行多个同时运行多个不同线程,不同线程,每个线程分别执行每个线程分别执行不同的任务不同的任务。16.1 线程简介线程简介n系统可以分配给每个进程系统可以分配给每个进程一段有限的使用一段有限的使用CPU的时间的时间(也可以称为(也可以称为CPU时间片时间片),),CPU在这段时间中执行某个进程,然后下一在这段时间中执行某个进程,然后下一个时间片又跳至另一个进程中去执行。个时间片又跳至另一个进程中去执行。n由于由于CPU转换较快,所以
6、使得每个进程好像转换较快,所以使得每个进程好像是是同时执行同时执行一样。一样。16.1 线程简介线程简介nWindows操作系统的执行模式操作系统的执行模式16.1 线程简介线程简介n一个线程则是进程中的执行流程,一个线程则是进程中的执行流程,一一个进程个进程中可以同时包括多个线程中可以同时包括多个线程,每个线程也可以,每个线程也可以得到得到一小段程序的执行时间一小段程序的执行时间,这样一个进程,这样一个进程就可以具有就可以具有多个并发执行的线程多个并发执行的线程。n在单线程中,程序代码按调用顺序依次往下在单线程中,程序代码按调用顺序依次往下执行,如果需要一个进程执行,如果需要一个进程同时同时
7、完成多段代码完成多段代码的操作的操作,就需要产生多线程。,就需要产生多线程。16.2 实现线程的两种方式实现线程的两种方式n16.2.1 继承继承Thread类类n16.2.2 实现实现Runnable接口接口16.2.1 继承继承THREAD类类n Thread类是类是java.lang包中的一个类,从这包中的一个类,从这个类中实例化的对象代表线程,个类中实例化的对象代表线程,程序员启动程序员启动一个新线程需要建立一个新线程需要建立Thread实例。实例。nThread类中常用的两个构造方法如下:类中常用的两个构造方法如下:public Thread(String threadName);p
8、ublic Thread();其中第一个构造方法是创建一个名称为其中第一个构造方法是创建一个名称为threadName的线程对象。的线程对象。16.2.1 继承继承THREAD类类n继承继承Thread类创建一个新的线程的语法如下:类创建一个新的线程的语法如下:public class ThreadTest extends Thread/.16.2.1 继承继承THREAD类类n如果需要创建线程应该先定义一个如果需要创建线程应该先定义一个Thread类类的子类的子类,并且,并且覆盖覆盖其中的其中的run()成员方法成员方法,将,将线程执行的程序代码写在其中。线程执行的程序代码写在其中。nThr
9、ead对象需要一个对象需要一个任务任务来执行,任务是指来执行,任务是指线程在启动时执行的工作,该工作的功能代线程在启动时执行的工作,该工作的功能代码被写在码被写在run()方法中。方法中。16.2.1 继承继承THREAD类类n这个这个run()方法必须使用如下这种语法格式:方法必须使用如下这种语法格式:public void run()/.注意:尽管在注意:尽管在Thread的子类中覆盖了的子类中覆盖了run()成员方法,但用户不能直接调用它,而是成员方法,但用户不能直接调用它,而是需要通过调用需要通过调用Thread类中的类中的start()方法间方法间接地使用它。接地使用它。16.2.1
10、 继承继承THREAD类类注意注意n1)start()方法的调用后方法的调用后并不是立即执行并不是立即执行多多线程代码,而是使得该线程变为线程代码,而是使得该线程变为可运行态可运行态(Runnable),什么时候运行是由操作系统决,什么时候运行是由操作系统决定的。定的。n2)如果)如果start()方法调用一个方法调用一个已经启动已经启动的线的线程,系统将抛出程,系统将抛出IllegalThreadStateException异异常。常。16.2.1 继承继承THREAD类类注意注意nmain()方法线程启动由方法线程启动由Java虚拟机负责,程虚拟机负责,程序员负责启动自己的线程。语法如下:
11、序员负责启动自己的线程。语法如下:public static void main(String args)new ThreadTest().start();public class MyThread extends Threadpublic void run()for(int n=1;n3;n+)for(int i=1;i4;i+)System.out.print(getName()+(+i+);System.out.println();System.out.println(exit from+getName();创建两个线程对象,分别实现重复显示创建两个线程对象,分别实现重复显示13数字的功
12、能数字的功能public class Test public static void main(String args)Thread t1=new MyThread();t1.setName(T1);Thread t2=new MyThread();t2.setName(T2);t1.start();t2.start();System.out.println(exit from+Thread.currentThread().getName();exit from mainT2(1)T1(1)T1(2)T1(3)T2(2)T1(1)T1(2)T1(3)T2(3)exit from T1T2(1)
13、T2(2)T2(3)exit from T216.2.2 实现实现RUNNABLE接口接口n到目前为止,线程都是通过扩展到目前为止,线程都是通过扩展Thread类来类来创建的,如果程序员需要创建的,如果程序员需要继承其他类继承其他类(非非Thread类类)并使该程序可以使用线程,就需并使该程序可以使用线程,就需要使用要使用Runnable接口接口。n实现实现Runnable接口的语法如下:接口的语法如下:public class MyThread extends Object implements Runnable16.2.2 实现实现RUNNABLE接口接口n实现实现Runnable接口的程
14、序会接口的程序会创建一个创建一个Thread对对象象,并将,并将Runnable对象与对象与Thread对象相关联。对象相关联。nThread类中有如下两个构造方法:类中有如下两个构造方法:public Thread(Runnable r)public Thread(Runnable r,String name)这两个构造方法的参数中都存在这两个构造方法的参数中都存在Runnable实例,使用以上构造方法就可以将实例,使用以上构造方法就可以将Runnable实例与实例与Thread实例相关联。实例相关联。16.2.2 实现实现RUNNABLE接口接口n使用使用Runnable接口启动新的线程的
15、步骤如接口启动新的线程的步骤如下:下:1)建立)建立Runnable对象。对象。2)使用参数为)使用参数为Runnable对象的构造方对象的构造方法创建法创建Thread实例。实例。3)调用)调用start()方法启动线程。方法启动线程。16.2.2 实现实现RUNNABLE接口接口16.2.2 实现实现RUNNABLE接口接口n通过通过Runnable接口创建线程时首先需要编接口创建线程时首先需要编写一个写一个实现实现Runnable接口的类接口的类,然后,然后实例实例化化该类的对象,这样就建立了该类的对象,这样就建立了Runnable对对象;象;n接下来使用相应的构造方法接下来使用相应的构
16、造方法创建创建Thread实例实例;n最后使用该实例调用最后使用该实例调用Thread类中的类中的Start()方方法法启动线程。启动线程。class MyThread2 implements Runnable private String name;public MyThread2(String name)this.name=name;public void run()for(int i=1;i10;i+)System.out.println(name+运行 :+i);public static void main(String args)new Thread(new MyThread2(t
17、h1).start();new Thread(new MyThread2(th2).start();/*MyThread2 th1=new MyThread2(th1);Thread thobj1=new Thread(th1);MyThread2 th2=new MyThread2(th2);Thread thobj2=new Thread(th2);thobj1.start();thobj2.start();*/16.2.2 实现实现RUNNABLE接口接口n实现实现Runnable接口比继承接口比继承Thread类所具有类所具有的优势:的优势:适合多个相同的程序代码的线程去处理适合多个相
18、同的程序代码的线程去处理同一个资源同一个资源可以避免可以避免java中的单继承的限制中的单继承的限制增加程序的健壮性,代码可以被多个线增加程序的健壮性,代码可以被多个线程共享,代码和数据独立程共享,代码和数据独立16.3 线程的生命周期线程的生命周期n线程生命周期中的各种状态线程生命周期中的各种状态16.3 线程的生命周期线程的生命周期n线程具有生命周期,其中包含线程具有生命周期,其中包含7种状态,分别种状态,分别为为出生状态、就绪状态、运行状态、等待状出生状态、就绪状态、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。态、休眠状态、阻塞状态和死亡状态。出生状态出生状态就是用户在创建线程时处
19、于的状就是用户在创建线程时处于的状态,在用户使用该线程实例调用态,在用户使用该线程实例调用start()方方法之前线程都处于出生状态;法之前线程都处于出生状态;当用户调用当用户调用start()方法后,线程处于方法后,线程处于就绪就绪状态状态(又被称为可执行状态);(又被称为可执行状态);当线程得到系统资源后就进入当线程得到系统资源后就进入运行状态运行状态。16.3 线程的生命周期线程的生命周期n一旦线程进入可执行状态,它会在就绪与运一旦线程进入可执行状态,它会在就绪与运行状态下辗转,同时也有可能进入行状态下辗转,同时也有可能进入等待、休等待、休眠、阻塞或死亡状态眠、阻塞或死亡状态。n当处于运
20、行状态下的线程调用当处于运行状态下的线程调用Thread类中的类中的wait()方法方法,该线程处于等待状态,进入等,该线程处于等待状态,进入等待状态的线程必须调用待状态的线程必须调用Thread类中的类中的notify()方法方法才能被唤醒,而才能被唤醒,而notifyAll()方法方法是将所是将所有处于等待状态下的线程唤醒;有处于等待状态下的线程唤醒;16.3 线程的生命周期线程的生命周期n当线程调用当线程调用Thread类中的类中的sleep()方法方法,则,则会进入会进入休眠状态休眠状态。n如果一个线程在运行状态下发出如果一个线程在运行状态下发出输入输入/输出请输出请求求,该线程将进入
21、,该线程将进入阻塞状态阻塞状态,在其等待输入,在其等待输入/输出结束时线程进入输出结束时线程进入就绪状态就绪状态,对于阻塞的,对于阻塞的线程来说,即使系统资源空闲,线程依然不线程来说,即使系统资源空闲,线程依然不能回到运行状态;能回到运行状态;n当线程的当线程的run()方法方法执行完毕时,线程进入执行完毕时,线程进入死死亡状态亡状态。16.3 线程的生命周期线程的生命周期n虽然多线程看起来像同时执行,但事实上虽然多线程看起来像同时执行,但事实上在在同一时间点上只有一个线程被执行同一时间点上只有一个线程被执行,只是线,只是线程之间切换较快,所以才会使人产生线程是程之间切换较快,所以才会使人产生
22、线程是同时进行的假象。同时进行的假象。n在在Windows操作系统中,系统会为每个线操作系统中,系统会为每个线程分配一小段程分配一小段CPU时间片,一旦时间片,一旦CPU时间片时间片结束结束就会将当前线程换为下一个线程,即使就会将当前线程换为下一个线程,即使该线程没有结束的情况下。该线程没有结束的情况下。16.3 线程的生命周期线程的生命周期n根据图根据图16-5所示,可以总结出使线程进入所示,可以总结出使线程进入阻阻塞状态塞状态有以下几种可能。有以下几种可能。调用调用sleep()方法方法。调用调用wait()方法方法。等待等待输入输入/输出输出完成。完成。16.3 线程的生命周期线程的生命
23、周期n当线程处于阻塞状态后,可通过以下几种方当线程处于阻塞状态后,可通过以下几种方式使线程再次进入式使线程再次进入就绪状态就绪状态。线程调用线程调用notify()方法方法。线程调用线程调用notifyAll()方法方法。线程调用线程调用interrupt()方法方法。线程的线程的休眠时间结束休眠时间结束。输入输入/输出结束输出结束。16.4 操作线程的方法操作线程的方法n16.4.1 线程的休眠线程的休眠n16.4.2 线程的加入线程的加入n16.4.3 线程的中断线程的中断n16.4.4 线程的礼让线程的礼让16.4.1 线程的休眠线程的休眠n一种能控制线程行为的方法是调用一种能控制线程行
24、为的方法是调用sleep()方方法法,sleep()方法需要一个参数用于指定该线方法需要一个参数用于指定该线程程休眠的时间休眠的时间,该时间使用毫秒为单位。,该时间使用毫秒为单位。n它通常是在它通常是在run()方法内的循环中被使用。方法内的循环中被使用。nsleep()方法的语法如下:方法的语法如下:tryThread.sleep(2000);catch(InterruptedException e)e.printStackTrace();public void run()System.out.println(开始执行线程。);System.out.println(进入睡眠状态。);try
25、Thread.sleep(3000);catch(InterruptedException e)e.printStackTrace();System.out.println(线程结束。);16.4.2 线程的加入线程的加入n如果当前某程序为多线程程序,假如存在一如果当前某程序为多线程程序,假如存在一个线程个线程A,现在需要插入线程,现在需要插入线程B,并要求线程,并要求线程B先执行完毕,然后再继续执行线程先执行完毕,然后再继续执行线程A,此时,此时可以使用可以使用Thread类中的类中的join()方法方法来完成。来完成。n这就好比此时正在看电视,却突然有人上门这就好比此时正在看电视,却突然有
26、人上门收水费,必须付完水费后才能继续看电视。收水费,必须付完水费后才能继续看电视。n当某个线程使用当某个线程使用join()方法加入到另外一个线方法加入到另外一个线程时,另一个线程会等待该线程执行完毕再程时,另一个线程会等待该线程执行完毕再继续执行。继续执行。16.4.2 线程的加入线程的加入n为什么要用为什么要用join()方法方法在很多情况下,主线程生成并起动了子线在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运程,如果子线程里要进行大量的耗时的运算,算,主线程往往将于子线程之前结束主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需但是如果主线程处理
27、完其他的事务后,需要用到子线程的处理结果,也就是要用到子线程的处理结果,也就是主线程主线程需要等待子线程执行完成之后再结束需要等待子线程执行完成之后再结束,这,这个时候就要用到个时候就要用到join()方法了。方法了。class Thread1 extends Thread private String name;public Thread1(String name)this.name=name;public void run()for(int i=0;i 3;i+)System.out.println(子线程+name+运行:+i);try sleep(int)Math.random()*1
28、0);catch(InterruptedException e)e.printStackTrace();public static void main(String args)System.out.println(Thread.currentThread().getName()+主线程运行开始!);Thread1 mTh1=new Thread1(A);Thread1 mTh2=new Thread1(B);mTh1.start();mTh2.start();System.out.println(Thread.currentThread().getName()+主线程运行结束!);main主线
29、程运行开始!main主线程运行结束!子线程B运行:0子线程A运行:0子线程B运行:1子线程A运行:1子线程B运行:2子线程A运行:2public static void main(String args)Thread1 mTh1=new Thread1(A);Thread1 mTh2=new Thread1(B);mTh1.start();mTh2.start();try mTh1.join();mTh2.join();catch(InterruptedException e)e.printStackTrace();main主线程运行开始!子线程B运行:0子线程B运行:1子线程B运行:2子线程
30、A运行:0子线程A运行:1子线程A运行:2main主线程运行结束!16.4.3 线程的中断线程的中断n以前使用以前使用stop()方法停止线程,但当前版本方法停止线程,但当前版本的的JDK早已废除了早已废除了stop()方法,同时也不建方法,同时也不建议使用议使用stop()方法来停止一个线程的运行。方法来停止一个线程的运行。n现在提倡在现在提倡在run()方法中使用无限循环的形式,方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。然后使用一个布尔型标记控制循环的停止。class ThreadTest extends Thread private int count=10;pub
31、lic void run()while(true)System.out.print(count+);if(-count=0)return;16.4.4 线程的礼让线程的礼让nThread类中使用类中使用yield()方法方法表示礼让,它只表示礼让,它只是给当前正处于运行状态下的线程一个提醒,是给当前正处于运行状态下的线程一个提醒,告知它告知它可以将可以将资源礼让给其他线程。但这仅资源礼让给其他线程。但这仅仅是一种暗示,没有任何一种机制保证当前仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。线程会将资源礼让。n对于支持多任务的操作系统来说,不需要调对于支持多任务的操作系统来说,不需要调用
32、用yeild()方法,因为操作系统会为线程自动方法,因为操作系统会为线程自动分配分配CPU时间片来执行。时间片来执行。16.5 线程的优先级线程的优先级n每个线程都具有各自的优先级,线程的优先每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,级可以在程序中表明该线程的重要性,如果如果有很多线程处于就绪状态,系统会根据优先有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态级来决定首先使哪个线程进入运行状态。n但这并不意味着低优先级的线程得不到运行,但这并不意味着低优先级的线程得不到运行,而只是它运行的几率比较小,比如垃圾回收而只是它运行的几率比较小,比
33、如垃圾回收线程的优先级就较低。线程的优先级就较低。16.5 线程的优先级线程的优先级nThread类中包含的静态成员变量代表了线程类中包含的静态成员变量代表了线程的某些优先级,比如的某些优先级,比如Thread.MIN_PRIORITY(常数(常数1)、)、Thread.MAX_PRIORITY(常数(常数10)、)、Thread.NORM_PRIORITY(常数(常数5)。)。n其中每个线程的优先级都在其中每个线程的优先级都在Thread.MIN_PRIORITY Thread.MAX_PRIORITY之间,在默认情况下其优先级都是之间,在默认情况下其优先级都是Thread.NORM_ PR
34、IORITY。每个新产生的线程。每个新产生的线程都继承了父线程的优先级。都继承了父线程的优先级。16.5 线程的优先级线程的优先级n在多任务操作系统中,每个线程都会得到一小在多任务操作系统中,每个线程都会得到一小段段CPU时间片运行,时间片运行,在时间结束时,将轮换另在时间结束时,将轮换另一个线程进入运行状态,这时系统会选择与当一个线程进入运行状态,这时系统会选择与当前线程优先级相同的线程予以运行。前线程优先级相同的线程予以运行。n系统始终选择就绪状态下优先级较高的线程进系统始终选择就绪状态下优先级较高的线程进入运行状态。入运行状态。n线程的优先级可以使用线程的优先级可以使用setPriori
35、ty()方法方法调整,调整,如果使用该方法设置的优先级不在如果使用该方法设置的优先级不在110之内,之内,将产生一个将产生一个IllegalArgumentException异常。异常。16.5 线程的优先级线程的优先级16.5 线程的优先级线程的优先级n在图在图16-9中,优先级为中,优先级为5的线程的线程A首先得到首先得到CPU时间片;当该时间结束后,轮换到与时间片;当该时间结束后,轮换到与线程线程A相同优先级的线程相同优先级的线程B;当线程;当线程B的运的运行时间结束后,会继续轮换到线程行时间结束后,会继续轮换到线程A,n直到线程直到线程A与线程与线程B都执行完毕,才会轮换都执行完毕,才
36、会轮换到线程到线程C;当线程;当线程C结束后,最后才会轮到结束后,最后才会轮到线程线程D。class MyThread51 extends Thread public MyThread51(String name)super(name);public void run()for(int i=0;i 0)try Thread.sleep(100);catch(Exception e)e.printStackTrace();System.out.println(tickets+num-);public static void main(String args)ThreadSafeTest t=ne
37、w ThreadSafeTest();Thread tA=new Thread(t);/以该类对象分别实例化4个线程 Thread tB=new Thread(t);Thread tC=new Thread(t);Thread tD=new Thread(t);tA.start();/分别启动线程 tB.start();tC.start();tD.start();tickets10tickets8tickets9tickets7tickets6tickets4tickets5tickets6tickets3tickets2tickets1tickets0tickets-1tickets-216
38、.6.2 线程同步机制线程同步机制n如何解决资源共享的问题?如何解决资源共享的问题?基本上所有解决多线程资源冲突问题都会基本上所有解决多线程资源冲突问题都会采用给定时间只采用给定时间只允许一个线程访问共享资允许一个线程访问共享资源源,这时就需要给共享资源上一道锁。,这时就需要给共享资源上一道锁。为了解决多线程并发操作可能引起的数据为了解决多线程并发操作可能引起的数据混乱,在混乱,在java中,引入对象中,引入对象“互斥锁互斥锁”,以保证共享数据操作的完整性。以保证共享数据操作的完整性。16.6.2 线程同步机制线程同步机制1)同步块同步块n在在Java中提供了同步机制,可以有效地防止中提供了同
39、步机制,可以有效地防止资源冲突。资源冲突。n同步机制使用同步机制使用synchronized关键字。关键字。synchronized(Object)nObject为任意一个对象为任意一个对象,每个对象都存在一个每个对象都存在一个标志位标志位,并具有两个值并具有两个值,分别为分别为0和和1。n一个线程运行到同步块时首先检查该对象的一个线程运行到同步块时首先检查该对象的标志位,如果为标志位,如果为0状态,表明此同步块中存在状态,表明此同步块中存在其他线程在运行。其他线程在运行。n这时该线程就处于就绪状态,直到这时该线程就处于就绪状态,直到处于同步处于同步块中的线程执行完同步块中的代码为止。块中的线
40、程执行完同步块中的代码为止。n这时该对象的标识位被设置为这时该对象的标识位被设置为1,该线程才能,该线程才能执行同步块中的代码,并将执行同步块中的代码,并将Object对象的标对象的标识位设置为识位设置为0,防止其他线程执行同步块中的,防止其他线程执行同步块中的代码。代码。public void run()synchronized()while(num 0)try Thread.sleep(100);catch(Exception e)e.printStackTrace();System.out.println(tickets+num-);16.6.2 线程同步机制线程同步机制2)同步方法同步
41、方法n同步方法就是在方法前面修饰同步方法就是在方法前面修饰synchronized关键字的方法,其语法如下。关键字的方法,其语法如下。synchronized void f()n当某个对象调用了同步方法,该对象上的其当某个对象调用了同步方法,该对象上的其他同步方法必须等待该同步方法执行完毕才他同步方法必须等待该同步方法执行完毕才能被执行。必须将每个能访问共享资源的方能被执行。必须将每个能访问共享资源的方法修饰为法修饰为synchronized,否则就会出错。,否则就会出错。16.6.2 线程同步机制线程同步机制n无论无论synchronized关键字加在方法上还是关键字加在方法上还是对象上,它
42、取得的对象上,它取得的锁都是对象锁都是对象,而不是把一,而不是把一段代码或函数当作锁。段代码或函数当作锁。n每个对象只有一个锁(每个对象只有一个锁(lock)与之相关联。)与之相关联。n实现同步是要很大的系统开销作为代价的,实现同步是要很大的系统开销作为代价的,甚至可能造成甚至可能造成死锁死锁,所以尽量避免无谓的同,所以尽量避免无谓的同步控制。步控制。16.7 线程间的通信线程间的通信n线程之间的通信使用线程之间的通信使用wait()、notify()以及以及notifyAll()方法实现。方法实现。n线程调用线程调用wait()方法后可以使该线程从运行方法后可以使该线程从运行状态进入就绪状态
43、状态进入就绪状态,sleep()方法也达到这方法也达到这样一个效果,那么两者究竟有何区别?样一个效果,那么两者究竟有何区别?n从同步的角度上来说,调用从同步的角度上来说,调用sleep()方法的方法的线程不释放锁,但调用线程不释放锁,但调用wait()方法的线程释方法的线程释放锁。放锁。16.7 线程间的通信线程间的通信n使用使用wait()方法有以下两种形式:方法有以下两种形式:wait(time)wait()第一种形式的第一种形式的wait()方法与方法与sleep()方法的方法的含义相同,都是指在此时间之内暂停;含义相同,都是指在此时间之内暂停;而第二种形式的而第二种形式的wait()方
44、法会使线程永久无方法会使线程永久无限地等待下去,需要使用限地等待下去,需要使用notify()或者或者notifyAll()方法唤醒。方法唤醒。16.7 线程间的通信线程间的通信nnotify()方法用来唤醒一个处于阻塞状态的方法用来唤醒一个处于阻塞状态的线程,任何一个已经满足了被唤醒条件的线线程,任何一个已经满足了被唤醒条件的线程都可能被唤醒。程都可能被唤醒。nnotifyAll()方法可以唤醒所有处于等待状态方法可以唤醒所有处于等待状态的线程。的线程。WAIT和和SLEEP区别区别n共同点:共同点:1.他们都是在多线程的环境下,都可以在他们都是在多线程的环境下,都可以在程序的调用处阻塞指定
45、的毫秒数,并返回。程序的调用处阻塞指定的毫秒数,并返回。2.wait()和和sleep()都可以通过都可以通过interrupt()方法方法 打断线程的暂停状态打断线程的暂停状态,从而使线程,从而使线程立刻抛出立刻抛出InterruptedException。WAIT和和SLEEP区别区别n如果线程如果线程A希望立即结束线程希望立即结束线程B,则可以对线,则可以对线程程B对应的对应的Thread实例调用实例调用interrupt方法。方法。如果此刻线程如果此刻线程B正在正在wait/sleep/join,则线,则线程程B会立刻抛出会立刻抛出InterruptedException,在,在cat
46、ch()中直接中直接return即可安全地结束线即可安全地结束线程。程。WAIT和和SLEEP区别区别n不同点:不同点:1.Thread类的方法:类的方法:sleep(),yield()等等 Object的方法:的方法:wait()和和notify()等等2.每个对象都有一个锁来控制同步访问。每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,关键字可以和对象的锁交互,来实现线程的同步。来实现线程的同步。sleep方法没有释放锁,而方法没有释放锁,而wait方法释放方法释放了锁,使得其他线程可以使用同步控制块了锁,使得其他线程可以使用同步控制块或者方法。或者方法。WAIT和和SLEEP区别区别n不同点:不同点:3.wait,notify和和notifyAll只能在同步控只能在同步控制方法或者同步控制块里面使用,而制方法或者同步控制块里面使用,而sleep可以在任何地方使用可以在任何地方使用4.sleep必须捕获异常,而必须捕获异常,而wait,notify和和notifyAll不需要捕获异常不需要捕获异常