关于java:话说-waitnotify-notifyAll

30次阅读

共计 13739 个字符,预计需要花费 35 分钟才能阅读完成。

一、前言

说起 java 的线程之间的通信,难免会想起它,他就是 wait、notify、notifyAll

  1. 他们三个都是 Object 类的办法,受到 final 和 native 加持,也就造就了他们是不能被重写的
  2. wait() 期待,象征让出以后线程的锁,进入期待状态,让其余线程先用会儿锁,这里留神了,什么叫让出以后线程的锁?也就是你以后线程必须要先取得锁,所以它个别会与 synchronized(我的上一篇文章有写)配合应用
    官网正文:The current thread must own this object’s monitor.
    wait 要抛出 InterruptedException 异样 须要 try catch 因为线程 wait 期间可能会被打断。
  3. notify() 唤醒一个 wait()的线程,当 notify 所在的代码块的锁开释之后,wait 的线程开始抢锁,嗯 ……. ,Object 类里正文写的是唤醒 wait 线程是任意 (arbitrary) 的 , 然而能够由具体实现自行裁决,我看 hotspot 实现如同是用的双向链表,notify 的时候是从 head 拿出一个唤醒,所以我称之为有序, 如果有问题请读者指出。
  4. notifyAll () 唤醒所有 wait 线程,notify 的高级版本
  5. 注意事项:并不是说 notify 之后 wait 的线程就能马上执行,因为 wait 是放弃了以后线程的锁,被 notify 之后还须要本人去抢锁,如果 notify 所在的代码块还没有抢到锁,或者被其余线程把锁抢到了,那 wait 所在线程还须要接着致力抢锁。

二、DEMO

1.wait notify 简略应用

是这样的, 小明做了饭,给二月鸟吃(备注:二月鸟 是一个人名),只有一双筷子,小明须要先尝一口看能不能吃,而后再告诉二月鸟吃饭,二月鸟要等小明放下筷子能力拿起筷子吃饭。用程序怎么实现,实现形式很多 咱明天只论 wait notify 其余形式靠边儿站

public class WaitNotifyTest {public static void main(String[] args)   {
        // 这是一把锁  筷子
        Object obj = new Object();

        new Thread(()->{synchronized (obj){
                try {System.out.println("二月鸟来了 等着吃饭...");
                    obj.wait();
                    System.out.println("二月鸟拿到筷子吃饭喽...");
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }).start();

        new Thread(()->{synchronized (obj){
                try{System.out.println("小明尝一下能够吃,告诉大家吃饭...");
                    // 告诉二月鸟 能够吃饭了
                    obj.notify();
                    System.out.println("这个时候小明还没有放下筷子...");
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println("小明放筷子了...");
                } catch (Exception e) {System.out.println("中毒了..");
                }
            }
        }).start();}
}

执行后果:二月鸟来了 等着吃饭...
小明尝一下能够吃,告诉大家吃饭...
这个时候小明还没有放下筷子...
小明放筷子了...
二月鸟拿到筷子吃饭喽...

这里须要留神几个点:

  1. wait 须要在 synchronized 中包裹着
  2. notify 须要 synchronized 中包裹着
  3. notify 之后 二月鸟没有马上拿起筷子吃饭,因为小明还没有放下筷子(锁还没开释)
  4. 这个故事里,小明有点儿不纯粹了,他还没筹备放筷子就告诉二月鸟能够吃饭了,害的二月鸟等了半天,咱们不能学小明,咱们平时写代码,个别业务执行完了,代码块最初执行 notify,执行完 notify 之后线程马上就会开释锁。

2. wait notifyAll 简略应用

还是 1 中的例子,小明做完饭后,二月鸟和小月月都来吃饭了, 还是只有一双筷子(真穷),这时候咱们用 wait notify 试一下 大家看看

public class WaitNotifyTest02 {public static void main(String[] args)   {
        // 这是一把锁  筷子
        Object obj = new Object();

        new Thread(()->{synchronized (obj){
                try {System.out.println("二月鸟来了 等着吃饭...");
                    obj.wait();
                    System.out.println("二月鸟拿到筷子吃饭喽...");
                } catch (InterruptedException e) {e.printStackTrace();
                }finally {System.out.println("二月鸟吃完了 放下筷子...");
                }
            }
        }).start();

        new Thread(()->{synchronized (obj){
                try {System.out.println("小月月来了 等着吃饭...");
                    obj.wait();
                    System.out.println("小月月拿到筷子吃饭喽...");
                } catch (InterruptedException e) {e.printStackTrace();
                }finally {System.out.println("小月月吃完了 放下筷子...");
                }
            }
        }).start();

        new Thread(()->{synchronized (obj){
                try{System.out.println("小明尝一下能够吃,告诉大家吃饭...");
                    // 告诉期待吃饭的一个人(留神是一个人)能够吃饭了
                    obj.notify();
                    System.out.println("这个时候小明还没有放下筷子...");
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println("小明放筷子了...");
                } catch (Exception e) {System.out.println("中毒了..");
                }
            }
        }).start();}
}

执行后果:二月鸟来了 等着吃饭...
小月月来了 等着吃饭...
小明尝一下能够吃,告诉大家吃饭...
这个时候小明还没有放下筷子...
小明放筷子了...
二月鸟拿到筷子吃饭喽...
二月鸟吃完了 放下筷子...

咦?小月月怎么不吃饭,二月鸟放下筷子了呀!

  1. notify 只告诉一个 wait 线程完结 wait 状态
  2. 这里能够看出 hotspot 实现 是依照 wait 的先后顺序告诉的
  3. 尽管是依照程序告诉的,然而咱们不能依赖这个法则,因为他仅仅是法则,在别的零碎(可能装置不同的 JVM 实现)上不肯定有这个法则

其余都不变,notify 改为 notifyAll

new Thread(()->{synchronized (obj){
                try{System.out.println("小明尝一下能够吃,告诉大家吃饭...");
                    // 告诉期待吃饭的人,所有人 能够吃饭了
                    obj.notifyAll();
                    System.out.println("这个时候小明还没有放下筷子...");
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println("小明放筷子了...");
                } catch (Exception e) {System.out.println("中毒了..");
                }
            }
        }).start();

执行后果:二月鸟来了 等着吃饭...
小月月来了 等着吃饭...
小明尝一下能够吃,告诉大家吃饭...
这个时候小明还没有放下筷子...
小明放筷子了...
小月月拿到筷子吃饭喽...
小月月吃完了 放下筷子...
二月鸟拿到筷子吃饭喽...
二月鸟吃完了 放下筷子...

这里能够看到:小月月竟然比二月鸟先吃到饭,这里是因为 notifyAll 是唤醒了所有人,谁抢到筷子(锁),谁先吃(执行)

通过我的测试,我发现大略法则是依照 wait 的反向程序来的,也就是先 wait 的后吃饭

三、伪装学术讨论

3.1 hotspot 实现,notify 是按 wait 程序的?

<u> 以下内容非虚构,但纯属个人见解,请勿当做实践来用,可作为参考 </u>

3.1.1 hotspot wait 代码(删减版)

// millis:wait 超时工夫  interruptible:是否可中断   TRAPS:调用 wait 的线程
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { 
  Thread * const Self = THREAD;
  JavaThread * jt = Self->as_Java_thread();

  //  这个办法就是判断以后线程是否取得了锁,如果不在 synchronized 代码块就会抛异样
  //  看 check_owner 办法
  CHECK_OWNER();  

  // 一堆代码 
  
  // 貌似是申请锁
  Thread::SpinAcquire(&_WaitSetLock, "WaitSet - add");
  // 这个就是把 wait 的线程放到一个“汇合”里  看 AddWaiter 办法
  AddWaiter(&node);
  // 貌似是开释锁 
  Thread::SpinRelease(&_WaitSetLock);
  // 一堆代码
}
// Returns true if the specified thread owns the ObjectMonitor.
// 翻译:如果指定的线程领有 ObjectMonitor 也就是取得了锁  就返回 true
// Otherwise returns false and throws IllegalMonitorStateException
// 翻译:否则返回 false 并且抛出异样 IllegalMonitorStateException
// (IMSE). If there is a pending exception and the specified thread
// is not the owner, that exception will be replaced by the IMSE.
// 这句不会翻译了
bool ObjectMonitor::check_owner(Thread* THREAD) {void* cur = owner_raw();
  if (cur == THREAD) {return true;}
  if (THREAD->is_lock_owned((address)cur)) {set_owner_from_BasicLock(cur, THREAD);  // Convert from BasicLock* to Thread*.
    _recursions = 0;
    return true;
  }
  // 这里抛出了异样  
  THROW_MSG_(vmSymbols::java_lang_IllegalMonitorStateException(),
             "current thread is not owner", false);
}

抛异样示例:

public class WaitNotifyTest04 {public static void main(String[] args)   {Object obj = new Object();
        new Thread(()->{
            try {obj.wait();
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }).start();}
}

异样信息:Exception in thread "Thread-0"       
    java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at WaitNotifyTest04.lambda$main$0(WaitNotifyTest04.java:23)
    at java.lang.Thread.run(Thread.java:748)

inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {// put node at end of queue (circular doubly linked list)
  // 翻译:把 node 放到队列的最初边(循环双向链表)// 如果你不晓得什么是循环双向链表 我给你画进去
  
  // _WaitSet 是头元素 其实玩儿过链表的人 这里应该都很分明 
  // 这里所谓的头 不是真正的头 只是一个绝对概念 
  // 如果是空的 那就一个 waiter ,_next _prev 都指向本人
  if (_WaitSet == NULL) {
    _WaitSet = node;
    node->_prev = node;
    node->_next = node;
  } else {
      
    // 如果曾经有了元素 那就把 node 放最初  
    // 而后 node 的_next 指向_WaitSet 也就是头元素  
    // node 的_prev 指向增加之前的头元素
    ObjectWaiter* head = _WaitSet;
    ObjectWaiter* tail = head->_prev;
    assert(tail->_next == head, "invariant check");
    tail->_next = node;
    head->_prev = node;
    node->_next = head;
    node->_prev = tail;
  }
}

循环双向链表:

不晓得大家有没有据说过 Disruptor 这个框架 , 他如同就是相似的构造,这个框架把性能做到了极致,有工夫大家能够理解下

3.1.2 hotspot notify 代码(删减版):


void ObjectMonitor::notify(TRAPS) {CHECK_OWNER();  // 检测是否领有锁
  // 如果没有 wait 线程 间接返回 
  // 这也是为什么  一个线程先 notify 另一个线程再 wait 是不能唤醒的 必须是先 wait 的线程能力被 notify
  // 
  if (_WaitSet == NULL) {return;}
  DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
  // 次要看这个 
  INotify(THREAD);
  OM_PERFDATA_OP(Notifications, inc(1));
}
// Consider:
// If the lock is cool (cxq == null && succ == null) and we're on an MP system
// 翻译:如果锁符合条件(cxq == null && succ == null) 并且在一个 MP 零碎 
// 说实话 我翻译不上来了,次要看下边这句 
// then instead of transferring a thread from the WaitSet to the EntryList
// 翻译:咱们将从 WaitSet 中取一个线程到 EntryList 
// EntryList 里是啥 就是唤醒的线程汇合  
// we might just dequeue a thread from the WaitSet and directly unpark() it.

void ObjectMonitor::INotify(Thread * Self) {Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify");
  // 次要看这 DequeueWaiter 就是从 wiatset 里取出一个 线程 
  // 怎么取?从头取 头是谁?第一个 wiat 的线程呗  看后边代码
  ObjectWaiter * iterator = DequeueWaiter();
  if (iterator != NULL) {
    // 一堆代码 
      
    // 这里须要留神 留神  留神  
    //1. 如果 list 是空 就把 list 指向 iterator 也就是取出来那个元素 
    // notify 的时候 是一个 走 if 
    if (list == NULL) {
      iterator->_next = iterator->_prev = NULL;
      _EntryList = iterator;
    } else {
      // 如果不是空 留神了 留神了  notifyAll 的时候 大多数会走这里 
      iterator->TState = ObjectWaiter::TS_CXQ;
      for (;;) {
        // 这里不是很懂 _cxq 貌似是指向的_EntryList 的第一个元素 
        ObjectWaiter * front = _cxq;
        iterator->_next = front;
        // cmpxchg : 如果 &_cxq 等于  front 就把 iterator 写到 &_cxq 内存
        // 这个操作时什么呢 把头上拿进去的 waiter 放到_EntryList 的首元素地位!!// 再品一下这句话  等会儿看 notifyAll 的时候 会用到这个常识
        if (Atomic::cmpxchg(&_cxq, front, iterator) == front) {break;}
      }
    }
    
    iterator->wait_reenter_begin(this);
  }
  Thread::SpinRelease(&_WaitSetLock);
}

inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {
  // dequeue the very first waiter
  // 人家都正文了 取出第一个 waiter 这就是为什么 notify 是按 wait 程序来的 
  ObjectWaiter* waiter = _WaitSet;
  if (waiter) {
    // 看这个办法 ↓ 
    DequeueSpecificWaiter(waiter);
  }
  return waiter;
}

inline void ObjectMonitor::DequeueSpecificWaiter(ObjectWaiter* node) {
  // when the waiter has woken up because of interrupt,
  // timeout or other spurious wake-up, dequeue the
  // waiter from waiting list
  ObjectWaiter* next = node->_next;
  // 如果_next 等于本人 那阐明就一个 waiter 
  // 闭眼想:是不是一个的时候本人的_next 是指向本人的 老光棍都懂,左手拉右手。。if (next == node) {_WaitSet = NULL;// 因为就一个 拿出后把 waitset 置空} else {
    // 这波操作 就是把头结点 毫无痕迹的取出来
    // 开端元素_next 指向 第二个元素 
    // 第二个元素的_prev 指向开端元素 
    ObjectWaiter* prev = node->_prev;
    next->_prev = prev;
    prev->_next = next;
    // 把_WaitSet 援用到 next 也就是第二个元素上  第一个元素拜拜了您嘞
    if (_WaitSet == node) {_WaitSet = next;}
  }
  // _next _prev 置空  留一个 node 孤零零  给大家画一下看一眼 
  node->_next = NULL;
  node->_prev = NULL;
}

取出前:

取出后:

3.1.3 hotspot notifyAll 代码(删减版)

// 这里代码简略  就是循环调用了 INotify() INotify 咱们曾经看过了 就是把 waiterset 的元素按程序取出来
// 一个一个放到 EntryList 的头部 
// 看我下边图示意 
// The current implementation of notifyAll() transfers the waiters one-at-a-time
// from the waitset to the EntryList. This could be done more efficiently with a
// single bulk transfer but in practice it's not time-critical. Beware too,
// that in prepend-mode we invert the order of the waiters. Let's say that the
// waitset is "ABCD" and the EntryList is "XYZ". After a notifyAll() in prepend
// mode the waitset will be empty and the EntryList will be "DCBAXYZ".

void ObjectMonitor::notifyAll(TRAPS) {CHECK_OWNER();  // Throws IMSE if not owner.
  if (_WaitSet == NULL) {return;}

  DTRACE_MONITOR_PROBE(notifyAll, this, object(), THREAD);
  int tally = 0;
  while (_WaitSet != NULL) {
    tally++;
    INotify(THREAD);
  }

  OM_PERFDATA_OP(Notifications, inc(tally));
}

waiterset 的连线我就少画点儿 凑活看:

看出什么端倪没有?waiter 的程序 到了 EntryList 变成了 顺叙 这也是为什么 我测试的时候,多个 wait 在执行完 notifyAll 的时候 是倒着获取到锁的,还是那句话 JVM 没有强制规定规定,所以不能以这个为根据进行业务的编写

只是大略理解一下实现原理。。而已

public class WaitNotifyTest05 {public static void main(String[] args) throws InterruptedException {Object obj = new Object();

        for (int i = 0; i < 20; i++) {new Thread(()->{synchronized (obj){
                    try {obj.wait();
                    } catch (InterruptedException e) {e.printStackTrace();
                    }finally {System.out.println(Thread.currentThread().getName()+"获取锁");
                    }
                }
            },"线程名称"+i).start();}

        TimeUnit.SECONDS.sleep(2);


        new Thread(()->{synchronized (obj){
                try{System.out.println("notifyAll");
                } catch (Exception e) {System.out.println("异样了");
                } finally {obj.notifyAll();
                }
            }
        }).start();}
}
输入后果:notifyAll  
线程名称 19 获取锁
线程名称 18 获取锁
线程名称 17 获取锁
线程名称 16 获取锁
线程名称 15 获取锁
线程名称 14 获取锁
线程名称 13 获取锁
线程名称 8 获取锁
线程名称 11 获取锁
线程名称 10 获取锁
线程名称 9 获取锁
线程名称 12 获取锁
线程名称 7 获取锁
线程名称 6 获取锁
线程名称 5 获取锁
线程名称 4 获取锁
线程名称 3 获取锁
线程名称 2 获取锁
线程名称 1 获取锁
线程名称 0 获取锁
    

3.2 相干面试题

3.2.1 sleep 与 wait 的区别

  1. sleep 属于 Thread 类,wait 属于 Object 类
  2. sleep 暂停以后线程指定工夫,让出 CPU 然而不会开释锁
  3. wait 会开释锁 只有被调用 notify/notifyAll 的时候,才可能接着执行,这里肯定是可能,因为他不肯定能抢到锁

3.2.2 wait、notify 模仿生产者和消费者

public class WaitNotifyTest06 {
     // 寄存生产数据的容器
     static LinkedList<String>  list= new LinkedList<>();
     // 容器最大寄存数
     static int maxCount = 1;
    public static void main(String[] args)   {
        // 生产者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果满了 wait  期待消费者生产了 告诉生产者 再生产
                    if (list.size() == maxCount) {
                        try {list.wait();
                        } catch (InterruptedException e) {e.printStackTrace();
                        }
                    }

                    // 生产了元素 告诉消费者 接着生产
                    String a = Double.valueOf(Math.random()*10000).intValue()+"";
                    list.add(a);
                    System.out.println("生产:"+a);
                    list.notify();}
            }
        },"生产者").start();

        // 消费者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果没有元素 wait
                    if (list.size() == 0) {
                        try{list.wait();
                        } catch (Exception e) {e.printStackTrace();
                        }
                    }
                    // 生产了 告诉生产者接着生产
                    System.out.println("生产:"+list.pop());
                    list.notify();}
            }
        },"消费者").start();}
}

留神:大家看到我这里判断 list 的大小 用的是 if(lsit.size() == maxCount ) 和 if(list.size() > = 0)

一个生产者和一个消费者 貌似没什么大问题

那如果 2 个消费者呢?

public class WaitNotifyTest07 {
     // 寄存生产数据的容器
     static LinkedList<String>  list= new LinkedList<>();
     // 容器最大寄存数
     static int maxCount = 1;
    public static void main(String[] args)   {
        // 生产者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果满了 wait  期待消费者生产了 告诉生产者 再生产
                    if (list.size() == maxCount) {
                        try {list.wait();
                        } catch (InterruptedException e) {e.printStackTrace();
                        }
                    }

                    // 生产了元素 告诉消费者 接着生产
                    String a = Double.valueOf(Math.random()*10000).intValue()+"";
                    list.add(a);
                    System.out.println("生产:"+a);
                    list.notify();}
            }
        },"生产者").start();

        // 消费者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果没有元素 wait
                    if (list.size() == 0) {
                        try{list.wait();
                        } catch (Exception e) {e.printStackTrace();
                        }
                    }
                    // 生产了 告诉生产者接着生产
                    System.out.println("生产 01:"+list.pop());
                    list.notify();}
            }
        },"消费者 01").start();

        // 消费者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果没有元素 wait
                    if (list.size() == 0) {
                        try{list.wait();
                        } catch (Exception e) {e.printStackTrace();
                        }
                    }
                    // 生产了 告诉生产者接着生产
                    System.out.println("生产 02:"+list.pop());
                    list.notify();}
            }
        },"消费者 02").start();}
}

异样喽:Exception in thread "消费者 02" java.util.NoSuchElementException
    at java.util.LinkedList.removeFirst(LinkedList.java:270)
    at java.util.LinkedList.pop(LinkedList.java:801)
    at WaitNotifyTest07.lambda$main$2(WaitNotifyTest07.java:63)
    at java.lang.Thread.run(Thread.java:748)  

为什么会这样呢?最容易想到的一条谬误门路是这样的:

  1. 生产者取得锁 生产“xxoo”
  2. 消费者 01 生产“xxoo”–> notify
  3. 这时候生产者 02 取得锁,判断 size() == 0 –>wait 开释锁
  4. 生产者取得锁,生产“xxxx”–> notify
  5. 消费者 01 取得锁 生产‘xxxx’–>notify
  6. 这是生产者 02 取得锁,然而这时候 size()==0 间接往下走 调用 pop 就会报错。
  7. 所以 个别咱们会用 while 代替 if,取得锁之后会再判断一下 wait 的条件,如果条件合乎再往下走
public class WaitNotifyTest07 {
     // 寄存生产数据的容器
     static LinkedList<String>  list= new LinkedList<>();
     // 容器最大寄存数
     static int maxCount = 1;
    public static void main(String[] args)   {
        // 生产者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果满了 wait  期待消费者生产了 告诉生产者 再生产
                    if (list.size() == maxCount) {
                        try {list.wait();
                        } catch (InterruptedException e) {e.printStackTrace();
                        }
                    }

                    // 生产了元素 告诉消费者 接着生产
                    String a = Double.valueOf(Math.random()*10000).intValue()+"";
                    list.add(a);
                    System.out.println("生产:"+a);
                    list.notify();}
            }
        },"生产者").start();

        // 消费者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果没有元素 wait
                    while (list.size() == 0) {
                        try{list.wait();
                        } catch (Exception e) {e.printStackTrace();
                        }
                    }
                    // 生产了 告诉生产者接着生产
                    System.out.println("生产:"+list.pop());
                    list.notify();}
            }
        },"消费者").start();

        // 消费者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果没有元素 wait
                    while (list.size() == 0) {
                        try{list.wait();
                        } catch (Exception e) {e.printStackTrace();
                        }
                    }
                    // 生产了 告诉生产者接着生产
                    System.out.println("生产 02:"+list.pop());
                    list.notify();}
            }
        },"消费者 02").start();}
}

咦?又有问题了。死锁了!!

因为一个生产者,两个消费者 须要用 notifyAll 代替 notify

为什么 notify 会死锁?轻易举例一种状况

  1. 生产者取得锁 生产“xxoo”,而后生产者又抢到锁 size() == 1 –> wait() 了
  2. 消费者 01 抢到锁 生产 xxoo 而后本人又抢到锁 size() == 0 — > wait()
  3. 这时候消费者 02 抢到锁 size() ==0 也 wai()了 都 wait 了
public class WaitNotifyTest07 {
     // 寄存生产数据的容器
     static LinkedList<String>  list= new LinkedList<>();
     // 容器最大寄存数
     static int maxCount = 1;
    public static void main(String[] args)   {
        // 生产者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果满了 wait  期待消费者生产了 告诉生产者 再生产
                    if (list.size() == maxCount) {
                        try {list.wait();
                        } catch (InterruptedException e) {e.printStackTrace();
                        }
                    }

                    // 生产了元素 告诉消费者 接着生产
                    String a = Double.valueOf(Math.random()*10000).intValue()+"";
                    list.add(a);
                    System.out.println("生产:"+a);
                    list.notifyAll();}
            }
        },"生产者").start();

        // 消费者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果没有元素 wait
                    while (list.size() == 0) {
                        try{list.wait();
                        } catch (Exception e) {e.printStackTrace();
                        }
                    }
                    // 生产了 告诉生产者接着生产
                    System.out.println("生产:"+list.pop());
                    list.notifyAll();}
            }
        },"消费者").start();

        // 消费者线程
        new Thread(()->{while (true) {synchronized (list){
                    // 如果没有元素 wait
                    while (list.size() == 0) {
                        try{list.wait();
                        } catch (Exception e) {e.printStackTrace();
                        }
                    }
                    // 生产了 告诉生产者接着生产
                    System.out.println("生产 02:"+list.pop());
                    list.notifyAll();}
            }
        },"消费者 02").start();}
}

最初附上本人公众号刚开始写 愿一起提高:

留神:以上文字 仅代表个人观点,仅供参考,如有问题还请指出,立刻马上连滚带爬的从被窝里进去改过。

正文完
 0