【多线程】一文图解wait()、notify()、join()源码

简介: 这一篇我们主要是对wait()、notify()、join()进行图解,可能有些粗糙,不足之处多多指出。

前言

大家好,我是小郭,这一篇我们主要是对wait()、notify()、join()进行图解,可能有些粗糙,不足之处多多指出。

概要

  1. wait()方法
  2. notify()方法
  3. join()方法

我们先对Object.wait()进行一波分析。

接着上一篇留下的问题

  1. 为什么调用Object.wait必须持有对象锁?
  2. Object.wait()被挂起后,是否会释放当前锁,让出CPU?

我们先来回答第一个问题

通过锁的原理,知道javap生成的字节码包含"monitorenter" 和"monitorexit",这里先不对锁进行扩展,我们先知道有这么一个东西就行。

这也是为什么wait需要先获取锁,才能获得monitor对象。

1. wait()方法

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

//ObjectMonitor的对象的结构体
ObjectMonitor::
ObjectMonitor() {
    _header = NULL;
    _count = 0;//用来记录该线程获取锁的次数
    _waiters = 0, 
    _recursions = 0;//锁的重入次数
    _object = NULL;
    _owner = NULL; //指向持有ObjectMonitor对象的线程
    _WaitSet = NULL;//存放处于wait状态的线程队列
    _WaitSetLock = 0;
    _Responsible = NULL;
    _succ = NULL;
    _cxq = NULL;
    FreeNext = NULL;
    _EntryList = NULL;//存放处于等待锁block状态的线程队列
    _SpinFreq = 0;
    _SpinClock = 0;
    OwnerIsThread = 0;
}

继续往下看,通过wait接口的解释来回答第二个问题

This method causes the current thread (call it T) toplace itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. 主要就是说,将对象放入waitSet中,然后放弃所有的同步声明,意思就是让出cpu。

在下才疏学浅,画了一个很粗糙的流程图😆,如果有不对的请求指出。

网络异常,图片无法展示
|
总结一下上面的流程

  1. 获取锁的monitor对象。
  2. 检测当前线程对象是否获取锁。
  3. 创建ObjectWaiter,将其状态设置为TS_WAIT。
  4. 操作_WaitSet链表,将当前的node节点尾部插入到队列中。
  5. 调用Exit()方法,退出monitor,同时释放该锁,让出CPU。
  6. 调用Park()方法,将线程挂起。
  7. 当ObjectWaiter状态为TS_WAIT,WaitSet移除当前node节点,修改状态为TS_RUN。
  8. 调用Enter(Self),重新抢占该锁。
  9. 退出当前等待monitor。

2. notify()方法

趁热打铁,我们再对Object.notify()进行一波分析。

网络异常,图片无法展示
|

总结一下上面的流程

  1. 线程A在wait() 后被加入了_WaitSet队列中。
  2. 线程C被线程B启动后竞争锁失败,被加入到_cxq队列的首位。
  3. 线程B在notify()时,从_WaitSet中取出第一个,根据Policy的不同,将这个线程放入_EntryList或者_cxq队列中的起始或末尾位置。
  4. 根据QMode的不同,将ObjectWaiter从_cxq或者_EntryList中取出后唤醒。

3. join()方法

可以看见join方法的核心还是wait,join方法利用了synchronized来修饰,就是因为wait方法必须获取锁。

Thread.join()
//等待该线程结束
public final void join() throws InterruptedException {
    join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) {
        //判断线程是否活着
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

我们来通过实例看一下join的使用

static class CreateRunable implements Runnable {
    public CreateRunable(int i) {
        this.i = i;
    }
    private int i;
    public int getI() {
        return i;
    }
    public void setI(int i) {
        this.i = i;
    }
    @Override
    public  void run() {
        synchronized (this){
            System.out.println("Runable接口,实现线程"+i);
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    for (int i = 0;i< 20 ;i++){
        Thread createThread = new Thread(new CreateRunable(i));
        createThread.start();
        createThread.join();
    }
    System.out.println("mian阻塞最后执行");
}

网络异常,图片无法展示
|

通过一个简单的流程图,就可以看到调用的过程

synchronized修饰在方法层,相当于synchronized(this),也就是createThread本身的实例。

主线程会持有这个对象的锁,然后去调用wait阻塞,谁调用谁阻塞,所以造成了主线程的阻塞,子线程在结束后会调用

nitifyAll()去唤醒主线程,主线程只要获取到了cpu和锁就可以继续执行。

总结

上面三个方法通过简单的流程图来描述源码的过程,我们可以看到主要是通过对锁的抢占来实现的线程等待和释放。

不足之处,希望大家能够多多指出,及时改正~

相关文章
|
6月前
|
安全 Java 调度
Netty源码—3.Reactor线程模型二
本文主要介绍了NioEventLoop的执行总体框架、Reactor线程执行一次事件轮询、Reactor线程处理产生IO事件的Channel、Reactor线程处理任务队列之添加任务、Reactor线程处理任务队列之执行任务、NioEventLoop总结。
|
6月前
|
安全 Java
Netty源码—2.Reactor线程模型一
本文主要介绍了关于NioEventLoop的问题整理、理解Reactor线程模型主要分三部分、NioEventLoop的创建和NioEventLoop的启动。
|
7月前
|
Java 中间件 调度
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
本文涉及InheritableThreadLocal和TTL,从源码的角度,分别分析它们是怎么实现父子线程传递的。建议先了解ThreadLocal。
249 4
【源码】【Java并发】从InheritableThreadLocal和TTL源码的角度来看父子线程传递
|
8月前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
339 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
12月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
230 12
|
12月前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
227 9
|
12月前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
130 3
|
11月前
|
Java 调度
|
11月前
|
Java API 调度
【JavaEE】——多线程(join阻塞,计算,引用,状态)
【JavaEE】——多线程,join,sleep引起的线程阻塞,多线程提升计算效率,如何获取线程的引用和状态
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
168 1

热门文章

最新文章