《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)
}


相关文章
|
24天前
|
Java 程序员
从菜鸟到大神:JAVA多线程通信的wait()、notify()、notifyAll()之旅
【6月更文挑战第21天】Java多线程核心在于wait(), notify(), notifyAll(),它们用于线程间通信与同步,确保数据一致性。wait()让线程释放锁并等待,notify()唤醒一个等待线程,notifyAll()唤醒所有线程。这些方法在解决生产者-消费者问题等场景中扮演关键角色,是程序员从新手到专家进阶的必经之路。通过学习和实践,每个程序员都能在多线程编程的挑战中成长。
|
5天前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
9 0
|
5天前
|
安全 Java 开发者
Java多线程:synchronized关键字和ReentrantLock的区别,为什么我们可能需要使用ReentrantLock而不是synchronized?
Java多线程:synchronized关键字和ReentrantLock的区别,为什么我们可能需要使用ReentrantLock而不是synchronized?
9 0
|
5天前
|
安全 Java
Java多线程中的锁机制:深入解析synchronized与ReentrantLock
Java多线程中的锁机制:深入解析synchronized与ReentrantLock
9 0
|
10天前
|
存储 SQL 安全
Java共享问题 、synchronized 线程安全分析、Monitor、wait/notify以及锁分类
Java共享问题 、synchronized 线程安全分析、Monitor、wait/notify以及锁分类
13 0
|
13天前
|
Java
Java中的线程通信:wait、notify与Condition详解
Java中的线程通信:wait、notify与Condition详解
|
24天前
|
Oracle Java 关系型数据库
面试知识点:notify是随机唤醒线程吗(唤醒线程顺序)?
面试知识点:notify是随机唤醒线程吗(唤醒线程顺序)?
18 0
|
5天前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
18 1
|
5天前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
14 1
|
3天前
|
缓存 Linux 编译器
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
11 0