好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java如何正确的使用wait-notify方法你知道吗

问题:

sleep() 和 wait() 有什么区别?

1. sleep(long n) 和 wait(long n) 的区别

(1) sleep 是 Thread 方法,而 wait 是 Object 的方法 ;

(2) sleep 不需要强制和 synchronized 配合使用,但 wait 需要 和 synchronized 一起用 ;

(3) sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁 ;

(4) 它们 状态 TIMED_WAITING;

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Slf4j

public class Test1 {

     private static final Object lock = new Object();

     public static void main(String[] args) throws InterruptedException {

         new Thread(()->{

             synchronized (lock){

                 log.debug( "t1线程获得锁...." );

                 try {

                     Thread.sleep( 20000 );

                 } catch (InterruptedException e) {

                     e.printStackTrace();

                 }

             }

         }, "t1" ).start();

         // 等待1s,让线程去执行t1线程

         Thread.sleep( 1000 );

         // 获取不到锁,因为t1线程在睡眠期间并不会释放锁

         synchronized (lock){

             log.debug( "主线程想要获取锁...." );

         }

     }

}

执行sleep()方法后的结果:在线程t1睡眠期间,主线程没有获得锁

10:34:34.574 [t1] DEBUG com.example.test.Test1 - t1线程获得锁....

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Slf4j

public class Test1 {

     private static final Object lock = new Object();

     public static void main(String[] args) throws InterruptedException {

         new Thread(()->{

             synchronized (lock){

                 log.debug( "t1线程获得锁...." );

                 try {

                     lock.wait();

                 } catch (InterruptedException e) {

                     e.printStackTrace();

                 }

             }

         }, "t1" ).start();

         // 等待1s,让线程去执行t1线程

         Thread.sleep( 1000 );

         // 获取不到锁,因为t1线程在睡眠期间并不会释放锁

         synchronized (lock){

             log.debug( "主线程想要获取锁...." );

         }

     }

}

执行wait()方法后的结果:线程t1等待20s,在线程t1等待期间,主线程获得了锁

10:36:22.723 [t1] DEBUG com.example.test.Test1 - t1线程获得锁....
10:36:23.721 [main] DEBUG com.example.test.Test1 - 主线程想要获取锁....

2. 正确使用wait-notify方法 [while(条件)+wait]

场景 :有几个小孩都想进入房间内使用算盘(CPU)进行计算,老王(操作系统)就使用了一把锁(synchronized)让同一时间只有一个小孩能进入房间使用算盘,于是他们排队进入房间。

(1) 小南最先获取到了锁,进入到房间内,但是由于条件不满足(没烟干不了活),小南不能继续进行计算 ,但小南如果一直占用着锁,其它人就得一直阻塞,效率太低。

(2) 于是老王单开了一间休息室(调用 wait 方法),让小南到休息室(WaitSet)等着去了,这时锁释放开, 其它人可以由老王随机安排进屋

(3) 直到小M将烟送来,大叫一声 [ 你的烟到了 ] (调用 notify 方法)

(4) 小南于是可以离开休息室,重新进入竞争锁的队列

下面我们看如何正确的实现这个场景

2.1 问题1

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

@Slf4j

public class Test2 {

     private static final Object room = new Object();

     // 是否有烟,默认没有

     private static boolean hasCigarette = false ;

     public static void main(String[] args) throws InterruptedException {

         new Thread(()->{

             synchronized (room){

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 if (!hasCigarette){

                     log.debug( "没烟,先歇会!" );

                     try {

                         Thread.sleep( 2000 );

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 }

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 if (hasCigarette){

                     log.debug( "有烟,[{}],可以开始干活了" , hasCigarette);

                 }

             }

         }, "小南" ).start();

         // 其他5个线程也想获取锁进入房间

         for ( int i= 0 ;i< 5 ;i++){

             new Thread(()->{

                 synchronized (room){

                     log.debug( "可以开始干活了" );

                 }

             }, "其他人" ).start();

         }

         // 主线程等待1s

         Thread.sleep( 1000 );

         // 因为小南线程使用sleep()方法,因此他在睡眠期间并不释放锁,送烟的没办法拿到锁进入房间送烟

         new Thread(()->{

             synchronized (room){

                 hasCigarette = true ;

             }

         }, "送烟的" ).start();

     }

}

执行结果:

11:10:50.556 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:10:50.565 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:10:52.565 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了

(1) 小南线程在睡眠期间并不释放锁,因此其他线程线程也没办法获取到锁进入房间,送烟线程就没办法送烟;

(2) 其它干活的线程,都要一直阻塞,效率太低 ;

要解决上述的问题,需要使用wait-notify机制

2.2 问题2

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

@Slf4j

public class Test2 {

     private static final Object room = new Object();

     // 是否有烟,默认没有

     private static boolean hasCigarette = false ;

     public static void main(String[] args) throws InterruptedException {

         new Thread(()->{

             synchronized (room){

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 if (!hasCigarette){

                     log.debug( "没烟,先歇会!" );

                     try {

                         room.wait();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 }

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 if (hasCigarette){

                     log.debug( "有烟,[{}],可以开始干活了" , hasCigarette);

                 }

             }

         }, "小南" ).start();

 

         // 其他5个线程也想获取锁进入房间

         for ( int i= 0 ;i< 5 ;i++){

             new Thread(()->{

                 synchronized (room){

                     log.debug( "可以开始干活了" );

                 }

             }, "其他人" ).start();

         }

 

         // 主线程等待1s

         Thread.sleep( 1000 );

 

         // 送烟的,唤醒正在睡眠的小南线程

         new Thread(()->{

             synchronized (room){

                 hasCigarette = true ;

                 room.notify();

             }

         }, "送烟的" ).start();

     }

}

执行结果:

11:21:36.775 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:21:36.780 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:37.773 [小南] DEBUG com.example.test.Test2 - 有烟没?[true]
11:21:37.774 [小南] DEBUG com.example.test.Test2 - 有烟,[true],可以开始干活了

解决了其他线程阻塞问题,但是如果有其他线程也在等待呢?就是说等待的线程不止小南一个,那么会不会唤醒错了呢?

2.3 问题3

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

@Slf4j

public class Test2 {

     private static final Object room = new Object();

     // 是否有烟,默认没有

     private static boolean hasCigarette = false ;

     // 是否有外卖,默认没有

     static boolean hasTakeout = false ;

     public static void main(String[] args) throws InterruptedException {

         new Thread(()->{

             synchronized (room){

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 if (!hasCigarette){

                     log.debug( "没烟,先歇会!" );

                     try {

                         room.wait();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 }

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 if (hasCigarette){

                     log.debug( "有烟,[{}],可以开始干活了" , hasCigarette);

                 } else {

                     log.debug( "没干成活.." );

                 }

             }

         }, "小南" ).start();

 

         new Thread(()->{

             synchronized (room){

                 log.debug( "有外卖没?[{}]" , hasTakeout);

                 if (!hasTakeout){

                     log.debug( "没外卖,先歇会!" );

                     try {

                         room.wait();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 }

                 log.debug( "有外卖没?[{}]" , hasTakeout);

                 if (hasTakeout){

                     log.debug( "有外卖,[{}],可以开始干活了" , hasTakeout);

                 } else {

                     log.debug( "没干成活.." );

                 }

             }

         }, "小女" ).start();

         // 主线程等待1s

         Thread.sleep( 1000 );

         new Thread(()->{

             synchronized (room){

                 hasTakeout = true ;

                 log.debug( "外卖到了...." );

                 room.notify();

             }

         }, "送外卖的" ).start();

     }

}

执行结果 :送外卖的应该叫醒小女但是却把小南叫醒了

11:31:50.989 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:31:50.994 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 有外卖没?[false]
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 没外卖,先歇会!
11:31:51.987 [送外卖的] DEBUG com.example.test.Test2 - 外卖到了....
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 没干成活..

notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】

2.4 问题4

解决方法:改为 notifyAll

?

1

2

3

4

5

6

7

new Thread(()->{

     synchronized (room){

         hasTakeout = true ;

         log.debug( "外卖到了...." );

         room.notifyAll();

     }

}, "送外卖的" ).start();

执行结果:

11:34:24.789 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:34:24.798 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:34:24.798 [小女] DEBUG com.example.test.Test2 - 有外卖没?[false]
11:34:24.802 [小女] DEBUG com.example.test.Test2 - 没外卖,先歇会!
11:34:25.794 [送外卖的] DEBUG com.example.test.Test2 - 外卖到了....
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外卖没?[true]
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外卖,[true],可以开始干活了
11:34:25.794 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:34:25.795 [小南] DEBUG com.example.test.Test2 - 没干成活..

从结果可以看出小女干成活,小南没有干成活。既然送烟的没到,小南应该继续等待才行,等送烟的来了再干活。

用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了

2.5 最终结果

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

@Slf4j

public class Test2 {

     private static final Object room = new Object();

     // 是否有烟,默认没有

     private static boolean hasCigarette = false ;

     // 是否有外卖,默认没有

     static boolean hasTakeout = false ;

     public static void main(String[] args) throws InterruptedException {

         new Thread(()->{

             synchronized (room){

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 while (!hasCigarette){

                     log.debug( "没烟,先歇会!" );

                     try {

                         room.wait();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 }

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 if (hasCigarette){

                     log.debug( "有烟,[{}],可以开始干活了" , hasCigarette);

                 } else {

                     log.debug( "没干成活.." );

                 }

             }

         }, "小南" ).start();

 

         new Thread(()->{

             synchronized (room){

                 log.debug( "有外卖没?[{}]" , hasTakeout);

                 if (!hasTakeout){

                     log.debug( "没外卖,先歇会!" );

                     try {

                         room.wait();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 }

                 log.debug( "有外卖没?[{}]" , hasTakeout);

                 if (hasTakeout){

                     log.debug( "有外卖,[{}],可以开始干活了" , hasTakeout);

                 } else {

                     log.debug( "没干成活.." );

                 }

             }

         }, "小女" ).start();

         // 主线程等待1s

         Thread.sleep( 1000 );

         // 送烟的,唤醒正在睡眠的小南线程

         new Thread(()->{

             synchronized (room){

                 hasTakeout = true ;

                 log.debug( "外卖到了...." );

                 room.notifyAll();

             }

         }, "送外卖的" ).start();

     }

}

@Slf4j

public class Test2 {

     private static final Object room = new Object();

     // 是否有烟,默认没有

     private static boolean hasCigarette = false ;

     // 是否有外卖,默认没有

     static boolean hasTakeout = false ;

     public static void main(String[] args) throws InterruptedException {

         new Thread(()->{

             synchronized (room){

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 while (!hasCigarette){

                     log.debug( "没烟,先歇会!" );

                     try {

                         room.wait();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 }

                 log.debug( "有烟没?[{}]" , hasCigarette);

                 if (hasCigarette){

                     log.debug( "有烟,[{}],可以开始干活了" , hasCigarette);

                 } else {

                     log.debug( "没干成活.." );

                 }

             }

         }, "小南" ).start();

 

         new Thread(()->{

             synchronized (room){

                 log.debug( "有外卖没?[{}]" , hasTakeout);

                 if (!hasTakeout){

                     log.debug( "没外卖,先歇会!" );

                     try {

                         room.wait();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 }

                 log.debug( "有外卖没?[{}]" , hasTakeout);

                 if (hasTakeout){

                     log.debug( "有外卖,[{}],可以开始干活了" , hasTakeout);

                 } else {

                     log.debug( "没干成活.." );

                 }

             }

         }, "小女" ).start();

         // 主线程等待1s

         Thread.sleep( 1000 );

         // 送烟的,唤醒正在睡眠的小南线程

         new Thread(()->{

             synchronized (room){

                 hasTakeout = true ;

                 log.debug( "外卖到了...." );

                 room.notifyAll();

             }

         }, "送外卖的" ).start();

     }

}

执行结果 :当没烟的时候,小南线程继续等待,等待下一次判断有烟的时候再干活

11:38:36.206 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:38:36.212 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 有外卖没?[false]
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 没外卖,先歇会!
11:38:37.205 [送外卖的] DEBUG com.example.test.Test2 - 外卖到了....
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外卖没?[true]
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外卖,[true],可以开始干活了
11:38:37.205 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!

使用wait-notify的正确姿势:

?

1

2

3

4

5

6

7

8

9

synchronized (lock) {

      while (条件不成立) {

          lock.wait();

      }

}

//另一个线程

synchronized (lock) {

     lock.notifyAll();

}

调用wait()和notify()系列方法进行线程通信的要点如下:

(1) 调用某个同步对象locko的wait()和notify()类型方法前,必须要取得这个锁对象的监视锁,所以wait()和notify()类型方法必须放在synchronized(locko)同步块中,如果没有获得监视锁,JVM就会报IllegalMonitorStateException异常。

(2) 调用wait()方法时使用while进行条件判断,如果是在某种条件下进行等待,对条件的判断就不能使用if语句做一次性判断,而是使用while循环进行反复判断。只有这样才能在线程被唤醒后继续检查wait的条件,并在条件没有满足的情况下继续等待。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注的更多内容!   

原文链接:https://hengheng.blog.csdn.net/article/details/123202383

查看更多关于Java如何正确的使用wait-notify方法你知道吗的详细内容...

  阅读:17次