美文网首页工作生活
Java基础建设 1-notify/wait方法

Java基础建设 1-notify/wait方法

作者: 折浪君 | 来源:发表于2019-07-03 19:13 被阅读0次

    一、使用用例

    public class ThreadTest {
        static final Object obj = new Object();
    
        private static boolean flag = false;
    
        public static void main(String[] args) throws Exception {
    
            Thread consume = new Thread(new Consume(), "Consume");
            Thread produce = new Thread(new Produce(), "Produce");
            consume.start();
            Thread.sleep(1000);
            produce.start();
    
            try {
                produce.join();//强制生产者先退出
                consume.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        // 生产者线程
        static class Produce implements Runnable {
    
            @Override
            public void run() {
    
                synchronized (obj) {
                    System.out.println("进入生产者线程");
                    System.out.println("生产");
                    try {
                        TimeUnit.MILLISECONDS.sleep(2000);  //模拟生产过程
                        flag = true;
                        obj.notify();  //通知消费者
                        System.out.println("通知消费者?");
                        TimeUnit.MILLISECONDS.sleep(1000);  //模拟其他耗时操作
                        System.out.println("退出生产者线程");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        //消费者线程
        static class Consume implements Runnable {
    
            @Override
            public void run() {
                System.out.println("进入消费者线程");
                System.out.println("wait flag 1:" + flag);
                synchronized (obj) {
                    while (!flag) {  //判断条件是否满足,若不满足则等待
                        try {
                            System.out.println("还没生产,进入等待");
    
                            obj.wait();
    
                            System.out.println("结束等待");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                System.out.println("wait flag 2:" + flag);
                System.out.println("消费");
                System.out.println("退出消费者线程");
            }
        }
    }
    

    运行结果

    进入消费者线程
    wait flag 1:false
    还没生产,进入等待
    进入生产者线程
    生产
    通知消费者?
    退出生产者线程
    结束等待
    wait flag 2:true
    消费
    退出消费者线程
    

    二、原理
    问题1:为什么wait/nofity需要配合synchronized使用
    问题2:明明消费者线程获得了锁,并没走完synchronized方法,生产者是如何进入到synchronized的?
    问题3:生产者是如何通知消费者的?
    问题4:生产者在调用notify的时候,消费者为何并没有被唤醒?

    synchronized:代码块通过javap生成的字节码中包含 monitorenter 和 monitorexit 指令,执行monitorenter指令可以获取对象的monitor
    查看Object.wait源码,上面有句注释

    This method should only be called by a thread that is the owner of this object's monitor
    

    @问题1
    Object.wait实际调用的是wait(0);wait(0)上面的注释写到

    This method causes the current thread (call it <var>T</var>) to  place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object
    

    意思就是wait方法会导致把当前线程放到wait set队列,并释放所有monitor对象,等待被唤醒
    @问题2
    ObjectMonitor:
    每个线程都有ObjectMonitor对象,ObjectMonitor对象维护了free和used的objectMonitor对象列表,如果当前free列表为空,将向全局global list请求分配ObjectMonitor
    WaitSet :处于wait状态的线程,会被加入到wait set;
    EntryList:处于等待锁block状态的线程,会被加入到entry set;

    ObjectWaiter:
    ObjectWaiter对象是双向链表结构,保存了_thread(当前线程)以及当前的状态TState等数据, 每个等待锁的线程都会被封装成ObjectWaiter对象。

    wait方法实现

    lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);实现:
    1、将当前线程封装成ObjectWaiter对象;
    2、通过ObjectMonitor::AddWaiter方法将ObjectWaiter添加到_WaitSet列表中;
    3、通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象。
    4、最终底层的park方法会挂起线程;
    @问题3

    notify:
    lock.notify()方法最终通过ObjectMonitor的void notify(TRAPS)实现:
    1、如果当前_WaitSet为空,即没有正在等待的线程,则直接返回;
    2、通过ObjectMonitor::DequeueWaiter方法,获取_WaitSet列表中的第一个ObjectWaiter节点,实现也很简单。
    这里需要注意的是,在jdk的notify方法注释是随机唤醒一个线程,其实是第一个ObjectWaiter节点
    3、根据不同的策略,将取出来的ObjectWaiter节点,加入到_EntryList或则通过Atomic::cmpxchg_ptr指令进行自旋操作cxq,具体代码实现有点长,这里就不贴了,有兴趣的同学可以看objectMonitor::notify方法;
    @问题4
    notifyAll:
    lock.notifyAll()方法最终通过ObjectMonitor的void notifyAll(TRAPS)实现:
    通过for循环取出_WaitSet的ObjectWaiter节点,并根据不同策略,加入到_EntryList或则进行自旋操作。

    从JVM的方法实现中,可以发现:notify和notifyAll并不会释放所占有的ObjectMonitor对象,其实真正释放ObjectMonitor对象的时间点是在执行monitorexit指令,一旦释放ObjectMonitor对象了,entry set中ObjectWaiter节点所保存的线程就可以开始竞争ObjectMonitor对象进行加锁操作了。

    相关文章

      网友评论

        本文标题:Java基础建设 1-notify/wait方法

        本文链接:https://www.haomeiwen.com/subject/buvthctx.html