《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)(一)

简介: 《JUC并发编程 - 高级篇》03 - 共享对象之管程 下篇(Monitor | wait&notify | Park&Unpark | 线程状态转换 | 活跃性 | ReentrantLock)

3.8 Monitor 概念

3.8.1 Java 对象头

以 32 位虚拟机为例

普通对象

|--------------------------------------------------------------|
|                  Object Header (64 bits)               |
|------------------------------------|-------------------------|
|         Mark Word (32 bits)        |  Klass Word (32 bits)   |
|------------------------------------|-------------------------|
  • klass word:指向类型元数据的指针。比如 student类型的对象,指向Student类型

数组对象

|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |   Klass Word(32bits)  |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|

其中 Mark Word 结构为

|-------------------------------------------------------|--------------------|
|                    Mark Word (32 bits)                |        State       |
|-------------------------------------------------------|--------------------|
|       hashcode:25      | age:4 | biased_lock:0 |  01  |       Normal       |
|-------------------------------------------------------|--------------------|
|   thread:23  | epoch:2 | age:4 | biased_lock:1 |  01  |      Biased        |
|-------------------------------------------------------|--------------------|
|           ptr_to_lock_record:30            |  00  | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|         ptr_to_heavyweight_monitor:30        |  10  | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                      |  11  |    Marked for GC   |
|-------------------------------------------------------|--------------------|

hashcode:每个对象都有一个hashcode值

age:垃圾回收时的分代年龄

biased_lock:是否偏向锁

加锁状态

ptr_to_heavyweight_monitor:指向操作系统中monitor的指针

ptr_to_lock_record:锁记录地址

以Integer对象为例,占 8 + 4 个字节(对象头 + value),基本类型int占 4个字节


3.8.2 原理之 Monitor(锁)

3.8.3 原理之 synchronized

3.8.4 小故事


故事角色

老王 - JVM

小南 - 线程

小女 - 线程

房间 - 对象

房间门上 - 防盗锁 - Monitor

房间门上 - 小南书包 - 轻量级锁

房间门上 - 刻上小南大名 - 偏向锁

批量重刻名 - 一个类的偏向锁撤销到达 20 阈值

不能刻名字 - 批量撤销该类对象的偏向锁,设置该类不可偏向

小南要使用房间保证计算不被其它人干扰(原子性),最初,他用的是防盗锁(Monitor),当上下文切换时,锁住门。这样,即使他离开了,别人也进不了门,他的工作就是安全的。— 重量级锁


但是,很多情况下没人跟他来竞争房间的使用权。小女是要用房间,但使用的时间上是错开的,小南白天用,小女晚上用。每次上锁太麻烦了,有没有更简单的办法呢?— 挂书包(轻量级锁)


小南和小女商量了一下,约定不锁门了,而是谁用房间,谁把自己的书包挂在门口,但他们的书包样式都一样,因此每次进门前得翻翻书包,看课本是谁的,如果是自己的,那么就可以进门,这样省的上锁解锁了。万一书包不是自己的,那么就在门外等,并通知对方下次用锁门的方式(存在竞争时,锁升级,轻量级 --> 重量级)。


后来,小女回老家了,很长一段时间都不会用这个房间。小南每次还是挂书包,翻书包,虽然比锁门省事了,但仍然觉得麻烦。


于是,小南干脆在门上刻上了自己的名字:【小南专属房间,其它人勿用】,(偏向锁)下次来用房间时,只要名字还在,那么说明没人打扰,还是可以安全地使用房间。如果这期间有其它人要用这个房间,那么由使用者将小南刻的名字擦掉,升级为挂书包的方式。(存在竞争时,锁升级,偏向锁 --> 轻量级锁)


同学们都放假回老家了,小南就膨胀了,在 20 个房间刻上了自己的名字,想进哪个进哪个。后来他自己放假回老家了,这时小女回来了(她也要用这些房间),结果就是得一个个地擦掉小南刻的名字,升级为挂书包的方式。老王(JVM)觉得这成本有点高,提出了一种批量重刻名的方法,他让小女不用挂书包了,可以直接在门上刻上自己的名字(批量重偏向)


后来,刻名的现象越来越频繁,老王受不了了:算了,这些房间都不能刻名了,只能挂书包(不可偏向)


3.8.5 原理之 synchronized 进阶

3.9 wait notify

3.9.1 小故事 - 为什么需要 wait

  • 由于条件不满足,小南不能继续进行计算
  • 但小南如果一直占用着锁,其它人就得一直阻塞,效率太低

77e966ae7d3ae0c7f5461f41eb4432cc.png

于是老王单开了一间休息室(调用 wait 方法),让小南到休息室(WaitSet)等着去了,但这时锁释放开,其它人可以由老王随机安排进屋


直到小M将烟送来,大叫一声 [ 你的烟到了 ] (调用 notify 方法)


406d554376b6e2f426cb78846b50b0b9.png


小南于是可以离开休息室,重新进入竞争锁的队列


1e27005fac2ff5e3d5db04e01a0d69bd.png


3.9.2 原理之 wait / notify


547d44ca958e0897b48cc1f47d2b727d.png


Owner 线程获取了锁,但是发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态


WAITING线程和BLOCKED线程的区别?


WAITING:已经获得了锁,但是条件不满足,释放锁后进入WaitSet队列;

BLOCKED:没有获得锁,进入EntryList中等待,状态是BLOCKED

==相同点:==都处于阻塞状态,不占用CPU时间片,将来调度时候也不会考虑。


WAITING线程和BLOCKED线程的唤醒条件?


WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争


BLOCKED 线程会在 Owner 线程释放锁时唤醒


3.9.3 API 介绍

image.png

它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法


wait:先成为Owner,才有资格进入WaitSet等待。


Notify:必须成为Owner,才能叫醒隔壁的WaitSet中的线程。


**案例一:**当没有获得锁,就使用wait/notify/notifyAll方法时,报IllegalMonitorStateException

@Slf4j(topic = "c.Test18")
public class Test18 {
    static final Object lock = new Object();
    public static void main(String[] args) {
        // synchronized (lock) { 
            try {
                lock.wait();//IllegalMonitorStateException
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        // }
    }
}

25d37859fe726e58551f1f1d48f85239.png


**案例二:**演示Notify和NotifyAll区别

@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    final static Object obj = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t1").start();
        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t2").start();
        // 主线程两秒后执行
        sleep(2);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
           obj.notify(); // 唤醒obj上一个线程
            // obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}

notify 的一种结果

15:38:17.518 c.TestWaitNotify [t1] - 执行....
15:38:17.520 c.TestWaitNotify [t2] - 执行....
15:38:19.519 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
15:38:19.519 c.TestWaitNotify [t1] - 其它代码....

notifyAll 的结果

15:37:19.908 c.TestWaitNotify [t1] - 执行....
15:37:19.910 c.TestWaitNotify [t2] - 执行....
15:37:21.908 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
15:37:21.908 c.TestWaitNotify [t2] - 其它代码....
15:37:21.908 c.TestWaitNotify [t1] - 其它代码....


3.9.4 API源码查看

wait() 和wait(long n) 的区别


wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify 为止

wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify


6d7aa1efebf8041e19187b2ec9373141.png

public final native void notify();
public final native void notifyAll();
public final void wait() throws InterruptedException {
    wait(0);
}
public final native void wait(long timeout) throws InterruptedException; //本地方法
public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
            "nanosecond timeout value out of range");
    }
  //可以看出wait(long timeout, int nanos)的nacos参数是无用的
    if (nanos > 0) {
        timeout++;
    }
    wait(timeout);//依旧调用的是wait(long timeout)
}


相关文章
|
2月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
170 2
|
10月前
|
缓存 安全 Java
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
347 6
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
254 9
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
149 3
|
12月前
|
Java 调度
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
185 1
|
2月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
156 6
|
5月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
306 83
|
2月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
276 0
|
3月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
252 16

热门文章

最新文章