本节包括wait(),notify(),notifyAll()介绍。以及为何notify,wait等方法要定义在Object中而不是Thread中。java
在Object中,定义了wait(), notify()和notifyAll()等接口。wait()的做用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的做用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒全部的线程。多线程
Object类中关于等待/唤醒的API详细信息以下:
notify() -- 唤醒在此对象监视器上等待的单个线程。
notifyAll() -- 唤醒在此对象监视器上等待的全部线程。
wait() -- 让当前线程处于“等待(阻塞)状态”,“直到其余线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) -- 让当前线程处于“等待(阻塞)状态”,“直到其余线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) -- 让当前线程处于“等待(阻塞)状态”,“直到其余线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其余某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。ide
// WaitTest.java的源码 class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { synchronized (this) { System.out.println(Thread.currentThread().getName()+" call notify()"); // 唤醒当前的wait线程 notify(); } } } public class WaitTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized(t1) { try { // 启动“线程t1” System.out.println(Thread.currentThread().getName()+" start t1"); t1.start(); // 主线程等待t1经过notify()唤醒。 System.out.println(Thread.currentThread().getName()+" wait()"); t1.wait(); System.out.println(Thread.currentThread().getName()+" continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果:函数
main start t1 main wait() t1 call notify() main continue
结果说明:oop
注意:t1.wait()方法时引发“当前线程”等待,直到另一个线程调用notify()或notifyAll()唤醒该线程。意思也就是t1.wait()让主线程(当前线程)等待而不是t1等待。源码分析
Object中的wait(), notify()等函数,和synchronized同样,会对“对象的同步锁”进行操做。this
wait()会使“当前线程”等待,由于线程进入等待状态,因此线程应该释放它锁持有的“同步锁”,不然其它线程获取不到该“同步锁”而没法运行!线程调用wait()以后,会释放它锁持有的“同步锁”;并且,根据前面的介绍,咱们知道:等待线程能够被notify()或notifyAll()唤醒。spa
责唤醒等待线程的那个线程(咱们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),而且调用notify()或notifyAll()方法以后,才能唤醒等待线程。虽然,等待线程被唤醒;可是,它不能马上执行,由于唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”以后,等待线程才能获取到“对象的同步锁”进而继续运行。线程
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,而且每一个对象有且仅有一个!这就是为何notify(), wait()等函数定义在Object类,而不是Thread类中的缘由。code
线程让步:让当前线程由“运行状态”进入到“就绪状态”,从而让其余具备相同优先级的等待线程获取执行权。可是在当前线程调用yield()以后,其余具备相同优先级的线程就必定能获取执行权,也有多是当前线程获取到了执行权从而又进入到“运行状态”继续执行。这与线程调度器有关。
// YieldTest.java的源码 class ThreadA extends Thread{ public ThreadA(String name){ super(name); } public synchronized void run(){ for(int i=0; i <10; i++){ System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i); // i整除4时,调用yield if (i%4 == 0) Thread.yield(); } } } public class YieldTest{ public static void main(String[] args){ ThreadA t1 = new ThreadA("t1"); ThreadA t2 = new ThreadA("t2"); t1.start(); t2.start(); } }
运行结果(每一次运行结果可能不一致):
t1 [5]:0 t2 [5]:0 t1 [5]:1 t1 [5]:2 t1 [5]:3 t1 [5]:4 t1 [5]:5 t1 [5]:6 t1 [5]:7 t1 [5]:8 t1 [5]:9 t2 [5]:1 t2 [5]:2 t2 [5]:3 t2 [5]:4 t2 [5]:5 t2 [5]:6 t2 [5]:7 t2 [5]:8 t2 [5]:9
结果说明:
“线程t1”在能被4整数的时候,并无切换到“线程t2”。这代表,yield()虽然可让线程由“运行状态”进入到“就绪状态”;可是,它不必定会让其它线程获取CPU执行权(即,其它线程进入到“运行状态”),即便这个“其它线程”与当前调用yield()的线程具备相同的优先级。
咱们知道,wait()的做用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而yield()的做用是让步,它也会让当前线程离开“运行状态”。它们的区别是:
(01) wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而不yield()是让线程由“运行状态”进入到“就绪状态”。
(02) wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。
线程休眠:当线程调用sleep()方法后,当前线程休眠,即当前线程从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程从新被唤醒时,它会从“阻塞状态”进入“就绪状态”,从而等待CPU的调度执行。
// SleepTest.java的源码 class ThreadA extends Thread{ public ThreadA(String name){ super(name); } public synchronized void run() { try { for(int i=0; i <10; i++){ System.out.printf("%s: %d\n", this.getName(), i); // i能被4整除时,休眠100毫秒 if (i%4 == 0) Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class SleepTest{ public static void main(String[] args){ ThreadA t1 = new ThreadA("t1"); t1.start(); } }
运行结果;
t1: 0 t1: 1 t1: 2 t1: 3 t1: 4 t1: 5 t1: 6 t1: 7 t1: 8 t1: 9
结果说明:
程序比较简单,在主线程main中启动线程t1。t1启动以后,当t1中的计算i能被4整除时,t1会经过Thread.sleep(100)休眠100毫秒。
咱们知道,wait()的做用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而sleep()的做用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态”。
可是,wait()会释放对象的同步锁,而sleep()则不会释放锁。
join() 定义在Thread.java中。
join() 的做用:让“主线程”等待“子线程”结束以后才能继续运行。
public final void join() throws InterruptedException { join(0); } public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
说明:
从代码中,咱们能够发现。当millis==0时,会进入while(isAlive())循环;即只要子线程是活的,主线程就不停的等待。
wait方法的做用是让“当前线程”等待,而这里的“当前线程”是指当前在CPU上运行的线程。因此,虽然是调用子线程的wait()方法,可是它是经过“主线程”去调用的;因此,休眠的是主线程,而不是“子线程”!而join方法时让主线程等待,不是CPU上执行的线程。
// JoinTest.java的源码 public class JoinTest{ public static void main(String[] args){ try { ThreadA t1 = new ThreadA("t1"); // 新建“线程t1” t1.start(); // 启动“线程t1” t1.join(); // 将“线程t1”加入到“主线程main”中,而且“主线程main()会等待它的完成” System.out.printf("%s finish\n", Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } static class ThreadA extends Thread{ public ThreadA(String name){ super(name); } public void run(){ System.out.printf("%s start\n", this.getName()); // 延时操做 for(int i=0; i <1000000; i++) ; System.out.printf("%s finish\n", this.getName()); } } }
运行结果:
t1 start t1 finish main finish
结果说明:
interrupt()的做用是中断本线程。
本线程中断本身是被容许的;其它线程调用本线程的interrupt()方法时,会经过checkAccess()检查权限。这有可能抛出SecurityException异常。
若是本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。
若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除而且会收到一个InterruptedException异常。例如,线程经过wait()进入阻塞状态,此时经过interrupt()中断该线程;调用interrupt()会当即将线程的中断标记设为“true”,可是因为线程处于阻塞状态,因此该“中断标记”会当即被清除为“false”,同时,会产生一个InterruptedException的异常。
若是线程被阻塞在一个Selector选择器中,那么经过interrupt()中断它时;线程的中断标记会被设置为true,而且它会当即从选择操做中返回。
若是不属于前面所说的状况,那么经过interrupt()中断线程时,它的中断标记会被设置为“true”。
中断一个“已终止的线程”不会产生任何操做。
一般,咱们经过“中断”方式终止处于“阻塞状态”的线程。
当线程因为被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。因为处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的为止就能终止线程,形式以下:
@Override public void run() { try { while (true) { // 执行任务... } } catch (InterruptedException ie) { // 因为产生InterruptedException异常,退出while(true)循环,线程终止! } }
说明:在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生InterruptedException中断。中断的捕获在while(true)以外,这样就退出了while(true)循环!
注意:对InterruptedException的捕获务通常放在while(true)循环体的外面,这样,在产生异常时就退出了while(true)循环。不然,InterruptedException在while(true)循环体以内,就须要额外的添加退出处理。形式以下:
@Override public void run() { while (true) { try { // 执行任务... } catch (InterruptedException ie) { // InterruptedException在while(true)循环体内。 // 当线程产生了InterruptedException异常时,while(true)仍能继续运行!须要手动退出 break; } } }
一般,咱们经过“标记”方式终止处于“运行状态”的线程。其中,包括“中断标记”和“额外添加标记”。
(01) 经过“中断标记”终止线程。
@Override public void run() { while (!isInterrupted()) { // 执行任务... } }
说明:isInterrupted()是判断线程的中断标记是否是为true。当线程处于运行状态,而且咱们须要终止它时;能够调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。
注意:interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。
(02) 经过“额外添加标记”终止线程
private volatile boolean flag= true; protected void stopTask() { flag = false; } @Override public void run() { while (flag) { // 执行任务... } }
说明:线程中有一个flag标记,它的默认值是true;而且咱们提供stopTask()来设置flag标记。当咱们须要终止该线程时,调用该线程的stopTask()方法就可让线程退出while循环。
注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程经过stopTask()修改了flag以后,本线程能看到修改后的flag的值。
综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式以下:
@Override public void run() { try { // 1. isInterrupted()保证,只要中断标记为true就终止线程。 while (!isInterrupted()) { // 执行任务... } } catch (InterruptedException ie) { // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。 } }
interrupt()经常被用来终止“阻塞状态”线程。参考下面示例:
// Demo1.java的源码 class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { try { int i=0; while (!isInterrupted()) { Thread.sleep(100); // 休眠100ms i++; System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException."); } } } public class Demo1 { public static void main(String[] args) { try { Thread t1 = new MyThread("t1"); // 新建“线程t1” System.out.println(t1.getName() +" ("+t1.getState()+") is new."); t1.start(); // 启动“线程t1” System.out.println(t1.getName() +" ("+t1.getState()+") is started."); // 主线程休眠300ms,而后主线程给t1发“中断”指令。 Thread.sleep(300); t1.interrupt(); System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted."); // 主线程休眠300ms,而后查看t1的状态。 Thread.sleep(300); System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now."); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果:
t1 (NEW) is new. t1 (RUNNABLE) is started. t1 (RUNNABLE) loop 1 t1 (RUNNABLE) loop 2 t1 (TIMED_WAITING) is interrupted. t1 (RUNNABLE) catch InterruptedException. t1 (TERMINATED) is interrupted now.
结果说明:
(01) 主线程main中经过new MyThread("t1")建立线程t1,以后经过t1.start()启动线程t1。
(02) t1启动以后,会不断的检查它的中断标记,若是中断标记为“false”;则休眠100ms。
(03) t1休眠以后,会切换到主线程main;主线程再次运行时,会执行t1.interrupt()中断线程t1。t1收到中断指令以后,会将t1的中断标记设置“false”,并且会抛出InterruptedException异常。在t1的run()方法中,是在循环体while以外捕获的异常;所以循环被终止。
6.总结
线程间同步机制以及中断机制就先探讨到这里,下面将有一个经典的生产-消费者问题来多线程的具体应用。