线程间通信之Object.wait/notify实现

简介: 线程间通信之Object.wait/notify实现

核心有这样几个关键字:锁,monitor以及指令。PS:遇到面试官问的问题不是你曾经考虑的问题不要怕,不要紧张。可能只是说法变了,但是知识还是那些知识!

【1】线程通信实例分析

先看一个线程通信例子:

public class WaitNotifyCase {
    public static void main(String[] args) {
        final Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread A is waiting to get lock");
                synchronized (lock) {
                    try {
                        System.out.println("thread A get lock");
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println("thread A do wait method");
                        lock.wait();
                        System.out.println("wait end");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread B is waiting to get lock");
                synchronized (lock) {
                    System.out.println("thread B get lock");
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                    System.out.println("thread B do notify method");
                }
            }
        }).start();
    }
}

很常见的synchronized、wait和notify。由同一个lock对象调用wait、notify方法。当线程A执行wait方法时,该线程会被挂起;当线程B执行notify方法时,会唤醒一个被挂起的线程A。

两个疑问


1、进入wait/notify方法之前,为什么要获取synchronized锁?

2、线程A获取了synchronized锁,执行wait方法并挂起,线程B又如何再次获取锁?

为什么使用synchronized

如下代码所示:

static void Sort(int [] array) {
    // synchronize this operation so that some other thread can't
    // manipulate the array while we are sorting it. This assumes that other
    // threads also synchronize their accesses to the array.
    synchronized(array) {
        // now sort elements in array
    }
}

查看其字节码:

synchronized代码块通过javap生成的字节码中包含 monitorentermonitorexit指令。

执行monitorenter指令可以获取对象的monitor,而lock.wait()方法通过调用native方法wait(0)实现,其中接口注释中有这么一句:

The current thread must own this object's monitor.


表示线程执行lock.wait()方法时,必须持有该lock对象的monitor,如果wait方法在synchronized代码中执行,该线程很显然已经持有了monitor。


代码执行过程分析


1、在多核环境下,线程A和B有可能同时执行monitorenter指令,并获取lock对象关联的monitor,只有一个线程可以和monitor建立关联,假设线程A执行加锁成功;

2、线程B竞争加锁失败,进入等待队列进行等待;

3、线程A继续执行,当执行到wait方法时,会发生什么?wait接口注释:

This method causes the current thread 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,等待被唤醒,并放弃lock对象上的所有同步声明。意味着线程A释放了锁,线程B可以重新执行加锁操作。不过又有一个疑问:在线程A的wait方法释放锁,到线程B获取锁,这期间发生了什么?线程B是如何知道线程A已经释放了锁?


4、线程B执行加锁操作成功,对于notify方法,JDK注释:notify方法会选择wait set中任意一个线程进行唤醒;

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

notifyAll方法的注释:notifyAll方法会唤醒monitor的wait set中所有线程。

Wakes up all threads that are waiting on this object's monitor.

5、执行完notify方法,并不会立马唤醒等待线程,在notify方法后面加一段sleep代码就可以看到效果,如果线程B执行完notify方法之后sleep 5s,在这段时间内,线程B依旧持有monitor,线程A只能继续等待。


那么wait set的线程什么时候会被唤醒?想要解答这些疑问, 需要分析jvm的相关实现,本文以HotSpot虚拟机1.7版本为例。

【2】什么是monitor?

在HotSpot虚拟机中,monitor采用ObjectMonitor实现。

每个线程都有两个ObjectMonitor对象列表,分别为free和used列表,如果当前free列表为空,线程将向全局global list请求分配ObjectMonitor。


ObjectMonitor对象中有两个队列:_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表;_owner指向获得ObjectMonitor对象的线程。_WaitSet :处于wait状态的线程,会被加入到wait set;

_EntryList:处于等待锁block状态的线程,会被加入到entry set;


【3】ObjectWaiter

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


【4】wait方法实现原理


lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);实现。


1、将当前线程封装成ObjectWaiter对象node;

2、通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中;

3、通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象。

4、最终底层的park方法会挂起线程。


【5】notify方法实现原理

ock.notify()方法最终通过ObjectMonitor的void notify(TRAPS)实现。


1、如果当前_WaitSet为空,即没有正在等待的线程,则直接返回;

2、通过ObjectMonitor::DequeueWaiter方法,获取_WaitSet列表中的第一个ObjectWaiter节点,实现也很简单。(这里需要注意的是,在jdk的notify方法注释是随机唤醒一个线程,其实是第一个ObjectWaiter节点。)


20190217151926209.png


3、根据不同的策略,将取出来的ObjectWaiter节点,加入到_EntryList或则通过Atomic::cmpxchg_ptr指令进行自旋操作cxq。


【6】notifyAll方法实现


lock.notifyAll()方法最终通过ObjectMonitor的void notifyAll(TRAPS)实现。

通过for循环取出_WaitSet的ObjectWaiter节点,并根据不同策略,加入到_EntryList或则进行自旋操作。


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


目录
相关文章
|
18天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
1月前
|
Python
如何在Python中实现线程之间的同步和通信?
【2月更文挑战第17天】【2月更文挑战第51篇】如何在Python中实现线程之间的同步和通信?
|
4月前
|
消息中间件 存储 Unix
进程间通信和线程间通信总结
写在前面 面试的时候一定不要疲劳战,比如上午面了一个,然后中午不休息直接赶到另外一个相距比较远的公司,影响状态。 面试的时候一定不要紧张,不管对方有几个人,总之面试的时候做好充分准备,休息好,放松心态。 好了,言归正传,开始总结。
39 0
|
1月前
|
设计模式 Java 调度
多案例理解Object的wait,notify,notifyAll与Thread的sleep,yield,join等方法
多案例理解Object的wait,notify,notifyAll与Thread的sleep,yield,join等方法
43 1
|
5月前
19.3 Boost Asio 多线程通信
多线程服务依赖于两个通用函数,首先`boost::bind`提供了一个高效的、简单的方法来创建函数对象和函数对象适配器,它的主要功能是提供了一种将函数和它的参数绑定到一起的方法,这种方法可以将具有参数的成员函数、普通函数以及函数对象转化为不带参数的函数对象。当参数绑定后则下一步就需要使用多线程功能,Boost库中提供了`boost::thread`库,`boost::thread`可以用于创建线程、启动线程、等待线程执行结束以及线程间通信等多种操,有了这两个关键库那么我们只需要`accept.accept(*sock)`等待套接字上线,当有套接字上线后则自动创建`MyThread`子线程,
47 0
19.3 Boost Asio 多线程通信
|
4月前
|
安全 Java 数据库连接
详细介绍线程间通信
详细介绍线程间通信 线程间通信是指在多线程编程中,不同的线程之间通过某种方式交换信息的过程。这是一个重要的概念,因为线程之间的协作是实现复杂并发系统的关键。 下面是一些线程间通信的常见方式和示例:
358 0
|
1月前
|
消息中间件 并行计算 网络协议
探秘高效Linux C/C++项目架构:让进程、线程和通信方式助力你的代码飞跃
探秘高效Linux C/C++项目架构:让进程、线程和通信方式助力你的代码飞跃
34 0
|
1月前
|
存储 Java 数据库连接
线程通信(CountDownLatch、CyclicBarrier、Semaphore、Exchanger)
线程通信(CountDownLatch、CyclicBarrier、Semaphore、Exchanger)
34 0
|
1月前
|
安全
多线程通信
多线程通信
|
2月前
|
Go 调度 开发者
Go语言并发基础:轻量级线程与通道通信
【2月更文挑战第6天】本文介绍了Go语言在并发编程方面的基础知识和核心概念。我们将深入探讨goroutine(轻量级线程)的创建与调度,以及如何利用channel进行goroutine间的通信与同步。此外,还将简要提及select语句的使用,并解释其在处理多个channel操作时的优势。

热门文章

最新文章