摘要:本文主要向大家介绍了【JAVA语言并发编程】之十二:线程间通信中notifyAll造成的早期通知问题(含代码),通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。
本文主要向大家介绍了【JAVA语言并发编程】之十二:线程间通信中notifyAll造成的早期通知问题(含代码),通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。
如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期通知,如果条件满足的时间很短,但很快又改变了,而变得不再满足,这时也将发生早期通知。这种现象听起来很奇怪,下面通过一个示例程序来说明问题。
很简单,两个线程等待删除List中的元素,同时另外一个线程正要向其中添加项目。代码如下:
[java] view plain copy
1. import java.util.*;
2.
3. public class EarlyNotify extends Object {
4. private List list;
5.
6. public EarlyNotify() {
7. list = Collections.synchronizedList(new LinkedList());
8. }
9.
10. public String removeItem() throws InterruptedException {
11. print("in removeItem() - entering");
12.
13. synchronized ( list ) {
14. if ( list.isEmpty() ) { //这里用if语句会发生危险
15. print("in removeItem() - about to wait()");
16. list.wait();
17. print("in removeItem() - done with wait()");
18. }
19.
20. //删除元素
21. String item = (String) list.remove(0);
22.
23. print("in removeItem() - leaving");
24. return item;
25. }
26. }
27.
28. public void addItem(String item) {
29. print("in addItem() - entering");
30. synchronized ( list ) {
31. //添加元素
32. list.add(item);
33. print("in addItem() - just added: '" + item + "'");
34.
35. //添加后,通知所有线程
36. list.notifyAll();
37. print("in addItem() - just notified");
38. }
39. print("in addItem() - leaving");
40. }
41.
42. private static void print(String msg) {
43. String name = Thread.currentThread().getName();
44. System.out.println(name + ": " + msg);
45. }
46.
47. public static void main(String[] args) {
48. final EarlyNotify en = new EarlyNotify();
49.
50. Runnable runA = new Runnable() {
51. public void run() {
52. try {
53. String item = en.removeItem();
54. print("in run() - returned: '" +
55. item + "'");
56. } catch ( InterruptedException ix ) {
57. print("interrupted!");
58. } catch ( Exception x ) {
59. print("threw an Exception!!!\n" + x);
60. }
61. }
62. };
63.
64. Runnable runB = new Runnable() {
65. public void run() {
66. en.addItem("Hello!");
67. }
68. };
69.
70. try {
71. //启动第一个删除元素的线程
72. Thread threadA1 = new Thread(runA, "threadA1");
73. threadA1.start();
74.
75. Thread.sleep(500);
76.
77. //启动第二个删除元素的线程
78. Thread threadA2 = new Thread(runA, "threadA2");
79. threadA2.start();
80.
81. Thread.sleep(500);
82. //启动增加元素的线程
83. Thread threadB = new Thread(runB, "threadB");
84. threadB.start();
85.
86. Thread.sleep(10000); // wait 10 seconds
87.
88. threadA1.interrupt();
89. threadA2.interrupt();
90. } catch ( InterruptedException x ) {}
91. }
92. }
执行结果如下:
分析:首先启动threadA1,threadA1在removeItem()中调用wait(),从而释放list上的对象锁。再过500ms,启动threadA2,threadA2调用removeItem(),获取list上的对象锁,也发现列表为空,从而在wait()方法处阻塞,释放list上的对象锁。再过500ms后,启动threadB,并调用addItem,获得list上的对象锁,并在list中添加一个元素,同时用notifyAll通知所有线程。
threadA1和threadA2都从wait()返回,等待获取list对象上的对象锁,并试图从列表中删除添加的元素,这就会产生麻烦,只有其中一个操作能成功。假设threadA1获取了list上的对象锁,并删除元素成功,在退出synchronized代码块时,它便会释放list上的对象锁,此时threadA2便会获取list上的对象锁,会继续删除list中的元素,但是list已经为空了,这便会抛出IndexOutOfBoundsException。
要避免以上问题只需将wait外围的if语句改为while循环即可,这样当list为空时,线程便会继续等待,而不会继续去执行删除list中元素的代码。
修改后的执行结果如下:
总结:在使用线程的等待/通知机制时,一般都要在while循环中调用wait()方法,满足条件时,才让while循环退出,这样一般也要配合使用一个boolean变量(或其他能判断真假的条件,如本文中的list.isEmpty()),满足while循环的条件时,进入while循环,执行wait()方法,不满足while循环的条件时,跳出循环,执行后面的代码。
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注编程语言JAVA频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号