JAVA线程同步wait()和notify()讲解
2016-09-13 15:14
519 查看
个人觉得Java线程同步有点比较难理解,单独拧出来讲解一下,结合实例更加容易理解。
wait()和notify()一系列的方法,是属于对象的,不是属于线程的。它们用在线程同步时,synchronized语句块中。
我们都知道,在synchronized语句块中,同一个对象,一个线程在执行完这一块代码之前,另一个线程,如果传进来的是同一个object,是不能进入这个语句块的。
也就是说,同一个对象是不能同时被两个线程用来进入synchronized中的,这就是线程同步。
废话不多说,先用通俗一点的语言来解释一下wait()和notify():
wait()意思是说,我等会儿再用这把锁,CPU也让给你们,我先休息一会儿!
notify()意思是说,我用完了,你们谁用?
也就是说,wait()会让出对象锁,同时,当前线程休眠,等待被唤醒,如果不被唤醒,就一直等在那儿。
notify()并不会让当前线程休眠,但会唤醒休眠的线程。
先看第一个例子!
两个run方法之间没有关系,但是,他们都用了同一个object!
仔细看,T1里面主要写了个wait(),而T2里面主要写了个notify()。
我们看到执行结果有两种:
第一种:
T1 start!
T2 start!
T2 end!
T1 end!
流程可以这样解释:
T1启动,让出锁,让出CPU,T2获得CPU,启动,唤醒使用了object的休眠的线程,T1被唤醒后等待启动,T2继续执行,T2执行完,T1获得CPU后继续执行。
第二种:
T2 start!
T2 end!
T1 start!
流程可以这样解释:
T2启动,唤醒使用了object的休眠的线程,虽然此时object并没有线程休眠,T2继续执行,T2执行完,T1获得CPU,启动,让出CPU,因为没有其它线程将它notify,所以结果就是无休止地等下去。
值得一提的是,再强调一遍:
wait会让出CPU而notify不会,notify重在于通知使用object的对象“我用完了!”,wait重在通知其它同用一个object的线程“我暂时不用了”并且让出CPUT。
所以说,看上面的顺序,
T2 start!
T2 end!
是连续的,说明它并没有因调用了notify而暂停!
那么,如果两个线程都写wait没有线程写notify会有什么现象呢?试一下就知道了。
结果是,
T1 start!
T2 start!
然后就是一直等待!
道理很显然,T1先启动,然后wait了,T2获得了锁和CPU,在没有其它线程与它竞争的情况下,T2执行了,然后也wait了。
在这里,两个线程都在等待,没有其它线程将它们notify,所以结果就是无休止地等下去!
至少说明了一点,wait后如果没有其它线程将它notify,是绝不可能重新启动的。不可能因为目前没有线程占用CPU,某一个正在等待的线程就自动重启。
下面,我再把它改一下,写四个线程,分别是
T1 wait()
T2 notify()
T3 notify()
T4 wait()
比如我这儿写的是按T1-T2-T3-T4的先后顺序先后start(),但实际上谁先启动,是有一定几率的。
执行上面代码,有两种结果:
一种是刚好wait两次,notify两次,notify在wait之后执行,刚好执行完。
另一种是,也是刚好wait两次,notify两次,但是,notify在wait之前执行,于是,至少会有一个线程由于后面没有线程将它notify而无休止地等待下去!
我摘选了两种情况的输出结果,仅供参考:
1、可以执行结束的情况:
T1 start!
T2 start!
T2 end!
T1 end!
T4 start!
T3 start!
T3 end!
T4 end!
执行流程是:
T1启动,wait,T2获得锁和CPU,T2宣布锁用完了其它线程可以用了,然后继续执行,T2执行完,T1被刚才T2唤醒后,等待T2执行完后,抢到了CPU,T1执行,
T1执行完,T4获得CPU,启动,wait,T3获得了锁和CPU,执行,宣布锁用完了,其它线程可以用了,然后继续执行,T3执行完,已经被唤醒并且等待已久的T4
得到CPU,执行。
2、不能执行结束,有线程由于没有其它线程唤醒,一直在等待中:
T1 start!
T2 start!
T2 end!
T1 end!
T3 start!
T3 end!
T4 start!
执行流程:
T1启动,wait,让出CPU和锁,T2得以启动。T2启动,并唤醒一个线程,自己继续执行。被唤醒的线程,也就是T1等待启动机会。
T2执行完,T1抢到了CPU,执行,并结束。
这时,只剩下T3和T4,在此时,两个线程的机会均等。
但是,T3抢到了CPU,于是它执行了,而且唤醒了线程,虽然此时并没有线程休眠。说白了,它浪费了一次notify。T3顺利执行完。
这时,终于轮到了T4,它启动了,wait了,但是,后面已经没有线程了,它的wait永远不会有线程帮它notify了!
于是,它就这么等着!
请仔细看执行流程,看懂,再自己做一下试验。
仔看看,你会看到,凡是当前线程的run方法里面写了notify,有了start马上就会end,而如果是写的wait,有了start,下一个绝对不是输出这个线程的end。
所以说,T2和T3由于是写的notify,它们的start和end总是成对出现。而T1和T4由于是写的wait,它们start后,下一个绝不可能是它的end。
最后再提醒一下,我们的wait和notify是针对同一个object的,而非线程。我们这一篇都在讲对象锁,而不是线程。
顺便说一下,如果没有线程在wait,调用notify是不会有什么问题的,就像这样:
T1 start!
T1 end!
下面举一个比较有趣的例子:
要求二个线程各自运行,中间不产生交互行为,等一个方法运行完了,在运行另外一个方法
...
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 46
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 47
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 48
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 49
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 50
我好想见到一个女孩呀,怎么没有呢!,次数: 1
我好想见到一个女孩呀,怎么没有呢!,次数: 2
我好想见到一个女孩呀,怎么没有呢!,次数: 3
我好想见到一个女孩呀,怎么没有呢!,次数: 4
...
它们之间没有交互的行为,就好像二个陌生人一样,各做各的,你们相识吧!
场景二 :要求二个线程之间相互交互
运行结果:
...
女孩问到:Hello,小子叫什么名字呢!男孩说到:我叫刘风!
女孩问到:Hello,小子叫什么名字呢!男孩说到:我叫刘风!
女孩问到:Hello,小子叫什么名字呢!男孩说到:我叫刘风!
女孩问到:Hello,小子叫什么名字呢!男孩说到:我叫刘风!
...
wait()和notify()一系列的方法,是属于对象的,不是属于线程的。它们用在线程同步时,synchronized语句块中。
我们都知道,在synchronized语句块中,同一个对象,一个线程在执行完这一块代码之前,另一个线程,如果传进来的是同一个object,是不能进入这个语句块的。
也就是说,同一个对象是不能同时被两个线程用来进入synchronized中的,这就是线程同步。
废话不多说,先用通俗一点的语言来解释一下wait()和notify():
wait()意思是说,我等会儿再用这把锁,CPU也让给你们,我先休息一会儿!
notify()意思是说,我用完了,你们谁用?
也就是说,wait()会让出对象锁,同时,当前线程休眠,等待被唤醒,如果不被唤醒,就一直等在那儿。
notify()并不会让当前线程休眠,但会唤醒休眠的线程。
先看第一个例子!
package com.multithread.wait; public class ThreadA { public static void main(String[] args) { final Object object = new Object(); Thread t1 = new Thread() { public void run() { synchronized (object) { System.out.println("T1 start!"); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1 end!"); } } }; Thread t2 = new Thread() { public void run() { synchronized (object) { System.out.println("T2 start!"); object.notify(); System.out.println("T2 end!"); } } }; t1.start(); t2.start(); } }这第一个例子很简单,写了两个线程(分别是两个类,两个run方法)。
两个run方法之间没有关系,但是,他们都用了同一个object!
仔细看,T1里面主要写了个wait(),而T2里面主要写了个notify()。
我们看到执行结果有两种:
第一种:
T1 start!
T2 start!
T2 end!
T1 end!
流程可以这样解释:
T1启动,让出锁,让出CPU,T2获得CPU,启动,唤醒使用了object的休眠的线程,T1被唤醒后等待启动,T2继续执行,T2执行完,T1获得CPU后继续执行。
第二种:
T2 start!
T2 end!
T1 start!
流程可以这样解释:
T2启动,唤醒使用了object的休眠的线程,虽然此时object并没有线程休眠,T2继续执行,T2执行完,T1获得CPU,启动,让出CPU,因为没有其它线程将它notify,所以结果就是无休止地等下去。
值得一提的是,再强调一遍:
wait会让出CPU而notify不会,notify重在于通知使用object的对象“我用完了!”,wait重在通知其它同用一个object的线程“我暂时不用了”并且让出CPUT。
所以说,看上面的顺序,
T2 start!
T2 end!
是连续的,说明它并没有因调用了notify而暂停!
那么,如果两个线程都写wait没有线程写notify会有什么现象呢?试一下就知道了。
结果是,
T1 start!
T2 start!
然后就是一直等待!
道理很显然,T1先启动,然后wait了,T2获得了锁和CPU,在没有其它线程与它竞争的情况下,T2执行了,然后也wait了。
在这里,两个线程都在等待,没有其它线程将它们notify,所以结果就是无休止地等下去!
至少说明了一点,wait后如果没有其它线程将它notify,是绝不可能重新启动的。不可能因为目前没有线程占用CPU,某一个正在等待的线程就自动重启。
下面,我再把它改一下,写四个线程,分别是
T1 wait()
T2 notify()
T3 notify()
T4 wait()
package com.multithread.wait; public class ThreadB { public static void main(String[] args) { final Object object = new Object(); Thread t1 = new Thread() { public void run() { synchronized (object) { System.out.println("T1 start!"); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1 end!"); } } }; Thread t2 = new Thread() { public void run() { synchronized (object) { System.out.println("T2 start!"); object.notify(); System.out.println("T2 end!"); } } }; Thread t3 = new Thread() { public void run() { synchronized (object) { System.out.println("T3 start!"); object.notify(); System.out.println("T3 end!"); } } }; Thread t4 = new Thread() { public void run() { synchronized (object) { System.out.println("T4 start!"); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T4 end!"); } } }; t1.start(); t2.start(); t3.start(); t4.start(); } }首先,大家知道,线程启动的顺序,和代码的先后顺序,理论上是没有关系的!
比如我这儿写的是按T1-T2-T3-T4的先后顺序先后start(),但实际上谁先启动,是有一定几率的。
执行上面代码,有两种结果:
一种是刚好wait两次,notify两次,notify在wait之后执行,刚好执行完。
另一种是,也是刚好wait两次,notify两次,但是,notify在wait之前执行,于是,至少会有一个线程由于后面没有线程将它notify而无休止地等待下去!
我摘选了两种情况的输出结果,仅供参考:
1、可以执行结束的情况:
T1 start!
T2 start!
T2 end!
T1 end!
T4 start!
T3 start!
T3 end!
T4 end!
执行流程是:
T1启动,wait,T2获得锁和CPU,T2宣布锁用完了其它线程可以用了,然后继续执行,T2执行完,T1被刚才T2唤醒后,等待T2执行完后,抢到了CPU,T1执行,
T1执行完,T4获得CPU,启动,wait,T3获得了锁和CPU,执行,宣布锁用完了,其它线程可以用了,然后继续执行,T3执行完,已经被唤醒并且等待已久的T4
得到CPU,执行。
2、不能执行结束,有线程由于没有其它线程唤醒,一直在等待中:
T1 start!
T2 start!
T2 end!
T1 end!
T3 start!
T3 end!
T4 start!
执行流程:
T1启动,wait,让出CPU和锁,T2得以启动。T2启动,并唤醒一个线程,自己继续执行。被唤醒的线程,也就是T1等待启动机会。
T2执行完,T1抢到了CPU,执行,并结束。
这时,只剩下T3和T4,在此时,两个线程的机会均等。
但是,T3抢到了CPU,于是它执行了,而且唤醒了线程,虽然此时并没有线程休眠。说白了,它浪费了一次notify。T3顺利执行完。
这时,终于轮到了T4,它启动了,wait了,但是,后面已经没有线程了,它的wait永远不会有线程帮它notify了!
于是,它就这么等着!
请仔细看执行流程,看懂,再自己做一下试验。
仔看看,你会看到,凡是当前线程的run方法里面写了notify,有了start马上就会end,而如果是写的wait,有了start,下一个绝对不是输出这个线程的end。
所以说,T2和T3由于是写的notify,它们的start和end总是成对出现。而T1和T4由于是写的wait,它们start后,下一个绝不可能是它的end。
最后再提醒一下,我们的wait和notify是针对同一个object的,而非线程。我们这一篇都在讲对象锁,而不是线程。
顺便说一下,如果没有线程在wait,调用notify是不会有什么问题的,就像这样:
public class ThreadG { public static void main(String[] args) { final Object object = new Object(); Thread t1 = new Thread() { public void run() { synchronized (object) { System.out.println("T1 start!"); object.notify(); System.out.println("T1 end!"); } } }; t1.start(); }输出如下:
T1 start!
T1 end!
下面举一个比较有趣的例子:
要求二个线程各自运行,中间不产生交互行为,等一个方法运行完了,在运行另外一个方法
package com.multithread.wait; public class SweetheartsTest { public static void main(String[] args) { new SweetheartsTest().init(); } private void init() { final Sweethearts sweethearts = new Sweethearts(); // 这二个方法不会产生交互,各执行各的,中间不会被打断 //synchronized作用于方法上,相当于在对象上加了锁 new Thread(new java.lang.Runnable() { public void run() { for (int i = 1; i <= 50; i++) { sweethearts.boy(i); } } }).start(); for (int i = 1; i <= 50; i++) { sweethearts.girl(i); } } class Sweethearts { synchronized void boy(int i) { System.out.println("我好想见到一个女孩呀,怎么没有呢!,次数: " + i); } synchronized void girl(int i) { System.out.println("怎么这条路上男孩子都没有一个呢!死哪了...,次数: " + i); } } }运行结果:
...
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 46
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 47
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 48
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 49
怎么这条路上男孩子都没有一个呢!死哪了...,次数: 50
我好想见到一个女孩呀,怎么没有呢!,次数: 1
我好想见到一个女孩呀,怎么没有呢!,次数: 2
我好想见到一个女孩呀,怎么没有呢!,次数: 3
我好想见到一个女孩呀,怎么没有呢!,次数: 4
...
它们之间没有交互的行为,就好像二个陌生人一样,各做各的,你们相识吧!
场景二 :要求二个线程之间相互交互
package com.multithread.wait; public class SweetheartsTest2 { public static void main(String[] args) { new SweetheartsTest2().init(); } private void init() { final Sweethearts bb = new Sweethearts(); // 这个类的二个方法,相互交互,很好的运用了wait与notify new Thread(new java.lang.Runnable() { public void run() { for (int i = 1; i <= 50; i++) { bb.boy(i); } } }).start(); for (int i = 1; i <= 50; i++) { bb.girl(i); } } class Sweethearts { boolean tag = false; synchronized void boy(int i) { //判断是否被女孩唤醒 while (!tag) { try { //继续等待女孩发问 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //女孩问了,男孩回答 System.out.println("男孩说到:我叫刘风!"); //设置为等待女孩发问的标记 tag = false; //唤醒女孩发问 notify(); } synchronized void girl(int i) { //判断是否被男孩唤醒 while (tag) { try { //等待男孩的回答 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //男孩回答完了,女孩又问他 System.out.print("女孩问到:Hello,小子叫什么名字呢!"); //设置为等待男孩回答的标记 tag = true; //唤醒男孩回答问题 notify(); } } }
运行结果:
...
女孩问到:Hello,小子叫什么名字呢!男孩说到:我叫刘风!
女孩问到:Hello,小子叫什么名字呢!男孩说到:我叫刘风!
女孩问到:Hello,小子叫什么名字呢!男孩说到:我叫刘风!
女孩问到:Hello,小子叫什么名字呢!男孩说到:我叫刘风!
...
相关文章推荐
- java线程的同步中notify和wait方法之生产消费实例讲解
- java线程同步原理及wait,notify的用法及与sleep的区别
- java线程同步(synchronized,wait,notify,notifyAll)
- JAVA线程同步中wait()和notify()
- java线程同步——synchronized (wait、notify)
- java线程同步问题(一个理解wait()与notify()的例子)
- Java线程同步阻塞, sleep(), suspend(), resume(), yield(), wait(), notify()
- java线程同步原理及wait,notify的用法及与sleep的区别
- JAVA线程同步中wait()和notify()简洁例子
- java线程同步:使用Object的wait,notify,notifyAll做线程调度
- JAVA线程同步中wait()和notify()简洁例子
- java线程同步原理及wait,notify的用法及与sleep的区别
- java线程等待、设置优先级、同步、挂起、恢复(Join、setPriority、synchronized、wait、notify……)
- Java线程同步阻塞, sleep(), suspend(), resume(), yield(), wait(), notify()
- java并发包中的Condition和Lock 取代Synchronized、wait、notify/notifyAll实现线程的同步与互斥
- java线程同步原理、wait,notify的用法及与sleep的区别
- java线程同步原理及wait,notify的用法及与sleep的区别
- java 线程中wait和notify使用讲解
- JAVA线程同步中的notify和wait()函数
- java线程同步原理及wait,notify的用法及与sleep的区别