美文网首页工作生活
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