神刀安全网

JAVA多线程11-基础篇-线程间通讯wait,notify


本章介绍 线程间的协作方式,主要包含wait(),notify(),notifyAll()方法的使用以及代码示例

一、等待/通知机制介绍

在多线程环境下,为了保证线程安全,我们使用同步锁来保证任何时刻只有一个线程可以访问共享资源。而在实际场景中,除了线程互斥,我们也希望线程间可以通过协作来解决问题,而java并没有手段使得线程间直接的交互,通常的做法是通过一个共享变量来实现线程间的通讯,例如:A线程需要读取B线程发送的信息,代码如下:

public class Demo1 {   private static String msg;   public static void main(String[] args) {         work();   }   public static void work() {       Thread t1 = new Thread(() -> {               System.out.println(msg);       });       Thread t2 = new Thread(() -> {               msg = "t2 set the msg";       });       t1.start();       t2.start();     } }  结果输出: null 或者 t2 set the msg 

结果说明:这个例子中线程t1,t2通过msg来通信,t1需要读取t2设置的msg,由于线程的执行无法预测,我们不能保证t1一定能读取t2设置的msg,所以会输出如上的结果。

如果我们想保证t1能够读取到t2设置的值,可以调整为如下代码:

public class Demo1 {     private static String msg;     public static void main(String[] args) {         work();     }     public static void work() {         Thread t1 = new Thread(() -> {             while (msg == null) {             }             System.out.println(msg + "!!!");         });         Thread t2 = new Thread(() -> {             msg = "t2 set the msg";         });         t1.start();         t2.start();     } }  结果输出: t2 set the msg!!! 

结果说明:t1通过死循环的方式直到msg有值为止,这种方式虽然能满足需求,但是非常耗CPU资源。

为了更好的实现线程间的交互,java提供了等待/通知机制,本质上也是通过锁来实现线程间的通信。这种机制是通过Object类的wait(),notify()方法来实现,下面对这几个方法进行介绍。

二、wait,notify,notifyAll方法介绍

/** * Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object. * In other words, this method behaves exactly as if it simply * performs the call {@code wait(0)}. * <p> * The current thread must own this object's monitor. The thread * releases ownership of this monitor and waits until another thread * notifies threads waiting on this object's monitor to wake up * either through a call to the {@code notify} method or the * {@code notifyAll} method. The thread then waits until it can * re-obtain ownership of the monitor and resumes execution. * <p> * As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop: * <pre> * synchronized (obj) { * while (<condition does not hold>) * obj.wait(); * ... // Perform action appropriate to condition * } * </pre> * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. */ public final native void wait() throws InterruptedException;  /** * Wakes up a single thread that is waiting on this object's * monitor. If any threads are waiting on this object, one of them * is chosen to be awakened. The choice is arbitrary and occurs at * the discretion of the implementation. A thread waits on an object's * monitor by calling one of the {@code wait} methods. * <p> * The awakened thread will not be able to proceed until the current * thread relinquishes the lock on this object. The awakened thread will * compete in the usual manner with any other threads that might be * actively competing to synchronize on this object; for example, the * awakened thread enjoys no reliable privilege or disadvantage in being * the next thread to lock this object. * <p> * This method should only be called by a thread that is the owner * of this object's monitor. A thread becomes the owner of the * object's monitor in one of three ways: * <ul> * <li>By executing a synchronized instance method of that object. * <li>By executing the body of a {@code synchronized} statement * that synchronizes on the object. * <li>For objects of type {@code Class,} by executing a * synchronized static method of that class. * </ul> * <p> * Only one thread at a time can own an object's monitor. */ public final native void notify();  /** * Wakes up all threads that are waiting on this object's monitor. A * thread waits on an object's monitor by calling one of the * {@code wait} methods. * <p> * The awakened threads will not be able to proceed until the current * thread relinquishes the lock on this object. The awakened threads * will compete in the usual manner with any other threads that might * be actively competing to synchronize on this object; for example, * the awakened threads enjoy no reliable privilege or disadvantage in * being the next thread to lock this object. * <p> * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. */ public final native void notifyAll(); 
wait,notify,notifyAll方法总结:
  • 这几个方法都定义在Object类中
  • 都是final方法,不能被重写
  • wait是阻塞方法,线程被中断会抛出InterruptedException异常。当前线程必须拥有对象锁,才能调用对象的wait方法,调用wait方法会使当前线程阻塞,当前线程会进入到对象的等待队列,同时释放对象锁,调用wait的伪代码如下:
synchronized (obj) { while (condition does not hold) obj.wait(); ... // do something }  wait(timeout) 在wait()的基础上增加了超时时间,超过该时间线程会被重新唤醒 
  • 调用notify、notifyAll方法的线程必须拥有对象锁,notify会唤醒等待对象监视器的单个线程,被唤醒的线程是随机的,notifyAll方法唤醒等待对象监视器的所有线程,被唤醒的线程(等待线程)不会立即竞争对象锁,只有当前的唤醒线程释放对象锁后,等待线程才能竞争对象锁

三、代码示例

先描述一个场景:
外婆家餐厅一直很火爆,我去过杭州西湖、北京APM、沈阳万象城这三家,味道都差不多,不过西湖边上的新白鹿感觉更胜一筹啊_,扯远了….
我们去外婆家吃饭,如果餐厅有空位,我们可以直接进去用餐,如果餐厅满员,我们需要排队,我们等着叫”XXX号,外婆让你回家吃饭了”,然后我们就可以进入餐厅吃饭,餐厅的容量实际是消费者和服务员之间的共享资源

这个场景抽象一下,可以认为用户是一个线程,而餐厅的服务人员相当于另外一个线程,餐厅是用餐者和服务人员的共享信息
代码如下:

public class WaitNotifyDemo1 {     public static void main(String[] args) {         final Queue queue = new LinkedList<>();         Thread producer = new Producer("producer", queue);         Thread consumer = new Consumer("consumer", queue);         producer.start();         consumer.start();     } }  public class Producer extends Thread {      private final Queue sharedQueue;//餐厅容量     public Producer(String name, Queue queue) {         super(name);         this.sharedQueue = queue;     }      @Override     public void run() {         //假设餐厅最多容纳10桌客人         for (int i = 0; i <= 10; i++) {             synchronized (sharedQueue) {                 while (sharedQueue.size() == 10) {                     try {                         sharedQueue.wait();                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 }                 sharedQueue.add(i);                 System.out.println("producer " + i);                 sharedQueue.notify();             }         }     } }  public class Consumer extends Thread {     private final Queue sharedQueue;     public Consumer(String name, Queue queue) {         super(name);         this.sharedQueue = queue;     }      @Override     public void run() {         //消费者持续不断的来就餐         while (true) {             synchronized (sharedQueue) {                 while (sharedQueue.size() == 0) {                     try {                         sharedQueue.wait();                         sleep(300);                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 }                 Integer o = (Integer) sharedQueue.poll();                 sharedQueue.notify();                 if (o == 10) {//只接纳10桌客人                     System.out.println("stop service");                     break;                 }             }         }     } } 结果输出: producer 0 producer 1 producer 2 producer 3 producer 4 producer 5 producer 6 producer 7 producer 8 producer 9 producer 10 stop service 

结果分析:

  1. 生产者和消费者共享一个队列Queue
  2. 核心流程都是先获取对象锁,然后根据条件判断是等待还是唤醒

四、几个问题

(1) 为什么调用wait,notify 方法都必须获得对像锁?
wait方法使当前线程挂起,notify会唤醒在该对象上挂起的线程,也就是说wait,notify一定是成对出现且针对同一个对象的挂起、唤醒操作,我们知道每个对象都有一个监视器对象,synchronized就是通过进入监视器来实现线程间互斥的,基于这个特性,恰好能使wait,notify建立关系,形成协作,所以通过对象锁,既能实现线程间互斥,也能实现线程间协作

(2)为什么 wait,notify,notifyAll都定义在Object类中?
因为wait,notify是依赖 同步锁进行协作的,而每个对象都有且仅有一把对象锁,所以定义在Object类中可以完美解决线程间协作的问题

(3)如果不在同步块或者同步方法中执行wait,notify会怎样?
大家可以自行验证一下,这里就不做实践了

五、总结

(1)等待/通知 机制是通过对象锁来实现的
(2)notify,notifyAll,wait必须在同步块或者同步方法中执行
(3)线程间的交互模型如图所示:

JAVA多线程11-基础篇-线程间通讯wait,notify

转载请注明作者及出处,并附上链接http://www.jianshu.com/u/ada8c4ee308b

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » JAVA多线程11-基础篇-线程间通讯wait,notify

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址