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

一、使用用例

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 T) 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方法)