Synchronized

是什么

Java中的关键字,主要用来加锁

怎么用

不管怎么用,最终锁的都是对象

1
2
3
4
5
6
7
//加到方法上
synchronized static f()锁的是类的class对象
synchronized void f() 锁的是类的实例对象
//代码块
synchronized(){

}

管程

即monitor(管程or监视器),用来实现多个线程对统同一资源的互斥访问

在Java中,每个对象都有Monitor,Monitor伴随Java对象一身

在hotsopt虚拟机中,monitor的实现为ObjectMonitor。
ObjectMonitor是基于c++来实现的,它有几个重要属性:

1
2
3
4
5
6
7
8
9
_owner:指向持有 ObjectMonitor 对象的线程

_WaitSet:存放处于 wait 状态的线程队列

_EntryList:存放处于等待锁 block 状态的线程队列

_recursions:锁的重入次数(控制可重入次数的)

_count:用来记录该线程获取锁的次数 (辅助判断锁的整体活跃度、竞争情况)

对象头的mark word

和ObjectMonitor的关系?

如果一个Java对象被线程持有,那么这个Java对象的对象头中的mark word的Lock word就会指向
ObjectMonitor的起始地址

重要级锁

在JDK1.6之前,synchronized的实现才会直接调用ObjectMonitor的enter和exit方法,这种锁被称为

重量级锁。主要实现是通过ObjectMonitor的关键属性来实现的,owner,EntryList,waitset,count

当多个线程同时访问同一段同步代码时,会先进入EntryList队列,当其中某个线程获取得到对象的Monitor后

进入owner区域并把Monitor中的_owner变量设置为当前线程,同时把Monitor中的计数器_count+1 即获得对

像锁。而其他线程这时候来获取锁,获取不到就处于变为阻塞状态。

当持有Monitor的线程调用了wait()方法后,那么他就会释放当前的Monitor,并将owner变量置为null,

count–,同时该线程进入waitset集合中等待被唤醒。若当前线程执行完毕也将释放monitor并复位变量的值

,以便其他线程进入获取monitor,这时候会去唤醒EntryList中的其他线程

monitor依赖于操作系统的mutexLock

为什么说他重?

Java的线程模型默认是一个用户线程对应着操作系统的一个内核线程。那么在synchronized中涉及到大

线程的阻塞和唤醒,这些都是从用户态切换到内核态来实现的,而这个切换操作在早期CPU是很耗时的

偏向锁

在JDK1.6引入,在JDK13默认为关闭了,JDK 15 完全禁用,JDK 17彻底移除代码

正确地来说,它并不是一个真正的锁。当第一个线程抢到锁时,会在java锁对象中的对象头的markword里填

入自己的线程名字。下个线程还获取时就会进行一个判断(这个判断操作是很轻的),如果还是同一个的话,

就会直接获取。不是的话,就得进行一个锁升级了

匿名偏向?

还没有人去持有偏向锁,这时候锁就是匿名的

为什么要有这个锁?

因为在通过Java开发团队的大量统计,其实在开发中很多代码都是同一个线程在执行

为什么偏向级锁不要设置为一开始就启动,默认是JVM启动后4s才会开启

如果一开始就明确就是多线程环境,那么偏向锁还有什么意义?JVM启动涉及到10多个线程,本身就是

多线程环境

为什么JDK 15要废弃偏向锁?

早期很多集合都是一把synchronized梭哈,比如vector,HashTable。不可否认,偏向锁能保证这些老集

合在单线程使用的环境下的性能,但后来随着HashMap,ArrayLIst等集合的出现。偏向锁变得不是很

重要了

而且官方还说偏向锁的引入导致代码的复杂度升高了,不好维护

微博禁用偏向锁,性能直接飙升

偏向锁这种东西会让对象迟迟无法被回收,导致对象一直越来越多,STW时间变长

锁撤销

轻量级锁

它的出现是为了优化重量级锁在竞争少的场景下的开销大的问题

当另外一个线程获取非匿名偏向锁时,偏向锁就会被撤销,锁就会自动升级为轻量级锁

轻量级锁状态时,JVM为锁对象的对象头markword预留了一部分空间,用来存储指向线程栈中Lock record

的指针

当一个线程尝试获取轻量级锁时(即发现对象头里的锁标记位为 00),就在线程虚拟机栈中开辟一快空间,

即Lock record,里面有两个部分

然后尝试通过CAS操作将对象头的mark word更新为指向锁记录的指针

自适应自旋?

锁膨胀

发生在轻量级—> 重量级锁

锁升级

锁升级过程

synchronized 的锁能降级吗?

对于HotSpot虚拟机来说,是没办法的。即一旦锁升级为重量级锁后,你的锁状态就会一直维持重量级锁,直到释放。

不过还有一种特殊的“降级”情况,即重量级锁的monitor对象不再被任何线程持有时,被清理和回收的过程。

JDK6对synchronized的优化?

自旋锁

JDK1.4引入,JDK1.6默认开启

锁消除

JIT层面的优化,即在使用synchronized的时候,如果JIT经过逃逸分析后发现并无线程安全的问题的话,

就会做锁消除

锁粗化

如果在一个循环里,频繁地获取资源释放资源,这样带来的消耗会很大,锁粗化会扩大锁的范围,把加锁

逻辑放到外面。

ps:锁粗化和“平时我们在开放中要尽可能地减少锁的粒度”矛盾吗?

不矛盾

锁升级

JDK1.6引入了偏向锁,轻量级锁。当线程竞争不激烈时,可以减少性能开销

Synchronized的缺点

无法知道线程是否获取到了锁

锁只有阻塞状态