事先声明 看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/64bit | Mark Word | 存储对象的hashCode、锁信息或分代年龄或GC标志等信息 |
| 32/64bit | Class 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有空才会理你,不然没用
本文详细解析了Java中synchronized关键字的工作原理,包括其如何利用对象监视器实现线程同步、可重入锁特性、对象头的结构以及wait/notify机制等关键概念。
5038

被折叠的 条评论
为什么被折叠?



