Java多线程中的wait(),notify()和sleep()

wait()和sleep()

我们先简单的了解一下wait()和sleep()这两个方法:

首先wait()是属于Object类的方法,从源码给出的解释来看,wait()方法可以做到如下几点:

(1)首先,调用了wait()之后会引起当前线程处于等待状状态。

(2)其次,每个线程必须持有该对象的monitor。如果在当前线程中调用wait()方法之后,该线程就会释放monitor的持有对象并让自己处于等待状态。

(3)如果想唤醒一个正在等待的线程,那么需要开启一个线程通过notify()或者notifyAll()方法去通知正在等待的线程获取monitor对象。如此,该线程即可打破等待的状态继续执行代码。

  1. /**
  2. * Causes the current thread to wait until another thread invokes the
  3. * {@link java.lang.Object#notify()} method or the
  4. * {@link java.lang.Object#notifyAll()} method for this object.
  5. * In other words, this method behaves exactly as if it simply
  6. * performs the call {@code wait(0)}.
  7. * <p>
  8. * The current thread must own this object's monitor. The thread
  9. * releases ownership of this monitor and waits until another thread
  10. * notifies threads waiting on this object's monitor to wake up
  11. * either through a call to the {@code notify} method or the
  12. * {@code notifyAll} method. The thread then waits until it can
  13. * re-obtain ownership of the monitor and resumes execution.
  14. * <p>
  15. * As in the one argument version, interrupts and spurious wakeups are
  16. * possible, and this method should always be used in a loop:
  17. * <pre>
  18. * synchronized (obj) {
  19. * while (<condition does not hold>)
  20. * obj.wait();
  21. * ... // Perform action appropriate to condition
  22. * }
  23. * </pre>
  24. * This method should only be called by a thread that is the owner
  25. * of this object's monitor. See the {@code notify} method for a
  26. * description of the ways in which a thread can become the owner of
  27. * a monitor.
  28. *
  29. * @exception IllegalMonitorStateException if the current thread is not
  30. * the owner of the object's monitor.
  31. * @exception InterruptedException if any thread interrupted the
  32. * current thread before or while the current thread
  33. * was waiting for a notification. The <i>interrupted
  34. * status</i> of the current thread is cleared when
  35. * this exception is thrown.
  36. * @see java.lang.Object#notify()
  37. * @see java.lang.Object#notifyAll()
  38. */
  39. public final void wait() throws InterruptedException {
  40. wait( 0);
  41. }

代码演示:

  1. public class Main {
  2. public static void main(String[] args) {
  3. Main main = new Main();
  4. main.startThread();
  5. }
  6. /**
  7. * 线程锁
  8. */
  9. private final Object object = new Object();
  10. /**
  11. * 启动线程
  12. */
  13. public void startThread() {
  14. Thread t = new Thread( new Runnable() {
  15. @Override
  16. public void run() {
  17. System.out.println( "开始执行线程。。。");
  18. System.out.println( "进入等待状态。。。");
  19. synchronized (object) {
  20. try {
  21. object.wait();
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. System.out.println( "线程结束。。。");
  27. }
  28. });
  29. t.start();
  30. }
  31. }

从代码来看,在执行线程和线程结束之间,我们先让该线程获取object对象作为自己的object's monitor,然后调用了object对象的wait()方法从而让其进入等待状态。那么程序运行的结果如下:


程序在未被唤醒之后,将不再打印“线程结束”,并且程序无法执行完毕一直处于等待状态。


sleep()方法来自于Thread类,从源码给出的解释来看,sleep()方法可以做到如下几点:

(1)首先,调用sleep()之后,会引起当前执行的线程进入暂时中断状态,也即睡眠状态。

(2)其次,虽然当前线程进入了睡眠状态,但是依然持有monitor对象。

(3)在中断完成之后,自动进入唤醒状态从而继续执行代码。

  1. /**
  2. * Causes the currently executing thread to sleep (temporarily cease
  3. * execution) for the specified number of milliseconds, subject to
  4. * the precision and accuracy of system timers and schedulers. The thread
  5. * does not lose ownership of any monitors.
  6. *
  7. * @param millis
  8. * the length of time to sleep in milliseconds
  9. *
  10. * @throws IllegalArgumentException
  11. * if the value of {@code millis} is negative
  12. *
  13. * @throws InterruptedException
  14. * if any thread has interrupted the current thread. The
  15. * <i>interrupted status</i> of the current thread is
  16. * cleared when this exception is thrown.
  17. */
  18. public static native void sleep(long millis) throws InterruptedException;


代码演示:

  1. public class Main {
  2. public static void main(String[] args) {
  3. Main main = new Main();
  4. main.startThread();
  5. }
  6. /**
  7. * 启动线程
  8. */
  9. public void startThread() {
  10. Thread t = new Thread( new Runnable() {
  11. @Override
  12. public void run() {
  13. System.out.println( "开始执行线程。。。");
  14. System.out.println( "进入睡眠状态。。。");
  15. try {
  16. Thread.sleep( 3000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println( "线程结束。。。");
  21. }
  22. });
  23. t.start();
  24. }
  25. }

从运行的结果来看,我们可以看出程序虽然在运行过程中中断了3秒,但是在3秒结束之后依然会继续执行代码,直到运行结束。在睡眠的期间内,线程会一直持有monitor对象。



那么从以上的理论和实践来分析,我们能得出如下结论:

(1)在线程的运行过程中,调用该线程持有monitor对象的wait()方法时,该线程首先会进入等待状态,并将自己持有的monitor对象释放。

(2)如果一个线程正处于等待状态时,那么唤醒它的办法就是开启一个新的线程,通过notify()或者notifyAll()的方式去唤醒。当然,需要注意的一点就是,必须是同一个monitor对象。

(3)sleep()方法虽然会使线程中断,但是不会将自己的monitor对象释放,在中断结束后,依然能够保持代码继续执行。


notify()和notifyAll()

说完了wait()方法之后,我们接下来讨论一下Object类中的另外两个与wait()相关的方法。首先还是通过源码的方式让大家先初步了解一下:
  1. /**
  2. * Wakes up a single thread that is waiting on this object's
  3. * monitor. If any threads are waiting on this object, one of them
  4. * is chosen to be awakened. The choice is arbitrary and occurs at
  5. * the discretion of the implementation. A thread waits on an object's
  6. * monitor by calling one of the {@code wait} methods.
  7. * <p>
  8. * The awakened thread will not be able to proceed until the current
  9. * thread relinquishes the lock on this object. The awakened thread will
  10. * compete in the usual manner with any other threads that might be
  11. * actively competing to synchronize on this object; for example, the
  12. * awakened thread enjoys no reliable privilege or disadvantage in being
  13. * the next thread to lock this object.
  14. * <p>
  15. * This method should only be called by a thread that is the owner
  16. * of this object's monitor. A thread becomes the owner of the
  17. * object's monitor in one of three ways:
  18. * <ul>
  19. * <li>By executing a synchronized instance method of that object.
  20. * <li>By executing the body of a {@code synchronized} statement
  21. * that synchronizes on the object.
  22. * <li>For objects of type {@code Class,} by executing a
  23. * synchronized static method of that class.
  24. * </ul>
  25. * <p>
  26. * Only one thread at a time can own an object's monitor.
  27. *
  28. * @exception IllegalMonitorStateException if the current thread is not
  29. * the owner of this object's monitor.
  30. * @see java.lang.Object#notifyAll()
  31. * @see java.lang.Object#wait()
  32. */
  33. public final native void notify();
先来看下notify()这个方法,通过阅读源码我们可以总结一下几点:
(1)当一个线程处于wait()状态时,也即等待它之前所持有的object's monitor被释放,通过notify()方法可以让该线程重新处于活动状态,从而去抢夺object's monitor,唤醒该线程。
(2)如果多个线程同时处于等待状态,那么调用notify()方法只能随机唤醒一个线程。
(3)在同一时间内,只有一个线程能够获得object's monitor,执行完毕之后,则再将其释放供其它线程抢占。
当然,如何使线程成为object‘s monitor的持有者,我会在多线程的其他博客中讲解。

接下来,我们再来看看notifyAll()方法:
  1. /**
  2. * Wakes up all threads that are waiting on this object's monitor. A
  3. * thread waits on an object's monitor by calling one of the
  4. * {@code wait} methods.
  5. * <p>
  6. * The awakened threads will not be able to proceed until the current
  7. * thread relinquishes the lock on this object. The awakened threads
  8. * will compete in the usual manner with any other threads that might
  9. * be actively competing to synchronize on this object; for example,
  10. * the awakened threads enjoy no reliable privilege or disadvantage in
  11. * being the next thread to lock this object.
  12. * <p>
  13. * This method should only be called by a thread that is the owner
  14. * of this object's monitor. See the {@code notify} method for a
  15. * description of the ways in which a thread can become the owner of
  16. * a monitor.
  17. *
  18. * @exception IllegalMonitorStateException if the current thread is not
  19. * the owner of this object's monitor.
  20. * @see java.lang.Object#notify()
  21. * @see java.lang.Object#wait()
  22. */
  23. public final native void notifyAll();
那么顾名思义,notifyAll()就是用来唤醒正在等待状态中的所有线程的,不过也需要注意以下几点:
(1)notifyAll()只会唤醒那些等待抢占指定object's monitor的线程,其他线程则不会被唤醒。
(2)notifyAll()只会一个一个的唤醒,而并非统一唤醒。因为在同一时间内,只有一个线程能够持有object's monitor
(3)notifyAll()只是随机的唤醒线程,并非有序唤醒。
那么如何做到有序唤醒是我们接下来要讨论的问题。

notify()实现有序唤醒的思路和实现

就上节提出的问题,我们在这节中可以进行一下思考和讨论。
首先,简单来说,我们需要去解决的其实就是对于多线程针对object's monitor的有序化。那么根据这一思路,我直接上代码:
  1. public class MyThreadFactory {
  2. // 线程A是否处于等待状态的标志
  3. private boolean isThreadAWaiting;
  4. // 线程B是否处于等待状态的标志
  5. private boolean isThreadBWaiting;
  6. // 线程C是否处于等待状态的标志
  7. private boolean isThreadCWaiting;
  8. public MyThreadFactory() {
  9. isThreadAWaiting = true;
  10. isThreadBWaiting = true;
  11. isThreadCWaiting = true;
  12. }
  13. /**
  14. * 对象锁
  15. */
  16. private final Object object = new Object();
  17. /**
  18. * 该线程作为一个唤醒线程
  19. */
  20. public void startWakenThread() {
  21. Thread t = new Thread( new Runnable() {
  22. @Override
  23. public void run() {
  24. synchronized (object) {
  25. System.out.println( "唤醒线程开始执行...");
  26. // 首先释放线程A
  27. quitThreadA();
  28. }
  29. }
  30. });
  31. t.start();
  32. }
  33. /**
  34. * 启动线程A
  35. */
  36. public void startThreadA() {
  37. Thread t = new Thread( new Runnable() {
  38. @Override
  39. public void run() {
  40. synchronized (object) {
  41. System.out.println( "线程A开始等待...");
  42. try {
  43. for (; ; ) {
  44. if (!isThreadAWaiting) break;
  45. object.wait();
  46. }
  47. } catch (InterruptedException e) {
  48. e.printStackTrace();
  49. }
  50. System.out.println( "线程A结束...");
  51. // 线程A结束后,暂停2秒释放线程B
  52. try {
  53. Thread.sleep( 2000);
  54. } catch (InterruptedException e) {
  55. e.printStackTrace();
  56. }
  57. quitThreadB();
  58. }
  59. }
  60. });
  61. t.start();
  62. }
  63. /**
  64. * 启动线程B
  65. */
  66. public void startThreadB() {
  67. Thread t = new Thread( new Runnable() {
  68. @Override
  69. public void run() {
  70. synchronized (object) {
  71. System.out.println( "线程B开始等待...");
  72. try {
  73. for (; ; ) {
  74. if (!isThreadBWaiting) break;
  75. object.wait();
  76. }
  77. } catch (InterruptedException e) {
  78. e.printStackTrace();
  79. }
  80. System.out.println( "线程B结束...");
  81. // 线程B结束后,暂停2秒释放线程C
  82. try {
  83. Thread.sleep( 2000);
  84. } catch (InterruptedException e) {
  85. e.printStackTrace();
  86. }
  87. quitThreadC();
  88. }
  89. }
  90. });
  91. t.start();
  92. }
  93. /**
  94. * 启动线程C
  95. */
  96. public void startThreadC() {
  97. Thread t = new Thread( new Runnable() {
  98. @Override
  99. public void run() {
  100. synchronized (object) {
  101. System.out.println( "线程C开始等待...");
  102. try {
  103. for (; ; ) {
  104. if (!isThreadCWaiting) break;
  105. object.wait();
  106. }
  107. } catch (InterruptedException e) {
  108. e.printStackTrace();
  109. }
  110. System.out.println( "线程C结束...");
  111. try {
  112. Thread.sleep( 1000);
  113. } catch (InterruptedException e) {
  114. e.printStackTrace();
  115. }
  116. System.out.println( "所有线程执行完毕!");
  117. }
  118. }
  119. });
  120. t.start();
  121. }
  122. /**
  123. * 线程A退出等待
  124. */
  125. private void quitThreadA() {
  126. isThreadAWaiting = false;
  127. object.notify();
  128. }
  129. /**
  130. * 线程B退出等待
  131. */
  132. private void quitThreadB() {
  133. isThreadBWaiting = false;
  134. object.notify();
  135. }
  136. /**
  137. * 线程C退出等待
  138. */
  139. private void quitThreadC() {
  140. isThreadCWaiting = false;
  141. object.notify();
  142. }
  143. }

在以上代码中,我写了三个线程A,B,C用来作为等待线程,并且最后通过一个唤醒线程来唤醒这三个线程。
我的思路是这样的:
(1)通过notify()方法可以保证每次只唤醒一个线程,但是不能确保唤醒的是哪个线程。
(2)在线程A,B,C中,添加for或者while循环的方式使其进入无限等待的状态。这样能够保证notify()无论如何都不能唤醒线程。
(3)分别给A,B,C线程设置各自的标记,如果要唤醒该线程的话,就改变其状态并且跳出死循环,在最后执行下一个线程。

那么最终调用的main函数如下:
  1. public static void main(String[] args) {
  2. MyThreadFactory factory = new MyThreadFactory();
  3. factory.startThreadA();
  4. factory.startThreadB();
  5. factory.startThreadC();
  6. try {
  7. Thread.sleep( 3000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. factory.startWakenThread();
  12. }

最后运行的结果如下:


小结

本章是为了通过wait(),sleep(),notify()以及notifyAll()去了解它们是如何使用的,以及它们各自的意义。现在在java.util.concurrent包中,已经有很多针对多线程并发的线程池封装类和接口,大家使用起来会更加灵活和方便,并且能够实现更多并发效果。因此不太推荐大家使用wait()和notify()这种方式

猜你喜欢

转载自blog.csdn.net/rjkkaikai/article/details/81016635