并发二 synchronized底层原理

本文详细解析了Java中synchronized关键字的工作原理,包括其如何利用对象监视器实现线程同步、可重入锁特性、对象头的结构以及wait/notify机制等关键概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

事先声明 看zejian博客:并发专题 受益良多
https://blog.youkuaiyun.com/javazejian/article/category/6940462


synchronized简介

1.java默认的线程同步关键字,可以满足JMM中的原子性和可见性,

2.隐式监视器锁 (显示锁为Lock,但lock不是监视器锁)

代码

由一段代码来引出原理

    //常见的同步代码块
    synchronized(lockObj){ 
        System.out.println(i); 
    }

    /**
    *对应javap后的 要执行这段字节码,需要能进入到monitor 持有锁 
        6: monitorenter//进入monitor 执行objectMonitor 
        //..省略无关代码 
        22: monitorexit//monitor退出 
        28: monitorexit //增加异常机制,确保异常时monitor也会退出 
        29: aload_2 
        30: athrow
    */

加了synchronized关键字后,同时只允许一个线程运行这段代码,

查看其编译后的字节码,发现增加了一监视器标识monitorenter/monitorexit,这个标识会要求:要进入这一段监控的代码 需要 持有监控对象lockObj需要的锁->对象监视器;

针对对象监视器,jvm维护了一些值用于处理同步,如下:

结构名称说明
_entrySet新人队列试图调用synchronized代码 monitorEntry
_owner监视器的所有者拥有对象监视器的线程
count监视器状态默认0 有线程进入则+1,重入再+1; 出重入的部分-1,线程出-1
_WaitSet等待队列调用wait之类的方法则阻塞线程进入waitSet

整个流程就如下图,进入entrySet,没有线程占用监视器count==0 或者占有的是线程就是当前线程,则acquire获取到资源 count++,否则线程就是挂起状态 同样waitSet队列也具有和entrySet同样的竞争机制,可以对比AQS的非公平锁

这里写图片描述

线程thread_a进入

->如果拥有监视器的线程monitor._owner == null -> 进入执行 monitor.count++,monitor._owner=thread_a当前线程拥有这个监视器;
如果monitor._owner == thread_a ->进入执行 monitor.count++ 即可重入锁
如果monitor._owner == 其他线程 ->等待执行 进入monitor._entrySet
->执行途中出现thread.wait 则进入monitor._waitSet等待序列 并monitor.count–;
->执行完成monitor._owner=null;

可重入锁

即syn(lockObj){调用其他的syn(lockObj)方法或块}的情况下,后面的同步快发现调用者持有正确的锁时,可以执行,
同时monitor.count++,执行完仍旧会–,当count==0时 owener=null 则其他线程就可以争夺了;

那这个监视器的信息是存放于哪的?synchronized(lockObj)中的lockObj中的对象头中

对象头和monitor

堆区的每个对象都包含对象头
头里主要有两部分,一部分指向方法区表明自己的身份
一部分记录一些Mark Word标记信息,如是否该GC的标记/分代年龄/hascode/锁信息
因为Mark Word全部记录的话占内存,故除基本信息外,其他信息会随着对象状态而做改变 比如可能是偏向锁->轻量级锁->重量级锁中的一种 或者没有
而1.6以前是只有重量级锁,synchronized通常说的就是重量级锁
在重量锁情况下,Mark Word的重量锁指针指向的是monitor
又因为monitor是基于操作系统的,操作系统实现线程之间的切换时需要从用户态转换到核心态,这种状态的切换时间成本高,所以是重量级锁
基于此,java6对重量锁进行了优化,就有了偏向锁,轻量级锁,自锁锁(即给自己加几十次空循环 避免被操作系统层面挂起),锁消除
这里写图片描述

虚拟机位数头对象结构说明
32/64bitMark Word存储对象的hashCode、锁信息或分代年龄或GC标志等信息
32/64bitClass Metadata Address类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。

因为监视器是在对象头中,故每个对象都可以维护者一个monitor,也因此每个对象都可以成为锁对象

原理总结

JVM隐式的对synchronized维护对象监视器达到线程安全的目的

wait/notify/notifyAll

1.接上文,wait/notify/notifyall实际操作的是监视器,主要作用在monitor_waitSet()上,所以这三个方法实质是监视器方法,进而这三个方法是Object的而不是Thread;
2.也因为同样的理由,调用wait/notify/notifyall需要在synchronized中,因为必须要获得锁对象操作监视器数据,否则运行时会抛出异常的监控状IllegalMonitorStateException

锁的范围

所有对象都可以成为锁对象,区别在于:
类/static上的锁是class对象
方法上的锁是this
代码块上的锁是指定的


//synchronized方法 字节码flags中会有标记 
public synchronized void test1(); 

/** 字节码如下
*descriptor: ()V 
*flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    针对方法上的jvm会在访问标志区的ACC_SYNCHRONIZED后,自动增加monitorentry和monitorexit,且monitorexit是在方法return/抛异常后都会执行的 
*/

线程中断与synchronized

线程在非阻塞状态调用t.interrupt()这个方法是不会中断线程的,当然thread.isInterrupt判断会阻塞线程,这个判断可以让中断生效 可以理解为当调用interrupt时,需要cpu有空才会理你,不然没用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值