简介
提到synchronized大家应该都知道是用来加锁的,被锁的代码块中的代码同时只能被一个线程执行,保证了代码的安全性。
基本用法
代码块
public void run() {
Object obj = new Object();
// 对指定的对象加锁
synchronized(obj) {
// 业务代码...
}
}
实例方法
// 用在实例方法上,相当于对当前对象this加锁
public synchronized void run() {
// 业务代码...
}
// 等同于
public void run() {
synchronized(this) {
// 业务代码...
}
}
静态方法
// 用在静态方法上,相当于对当前类对象this.getClass()加锁
public static synchronized void run() {
// 业务代码...
}
// 等同于
public void run() {
synchronized(this.getClass()) {
// 业务代码...
}
}
锁状态转换
上一篇文章中提到对象头中的Mark Word和synchronized密切相关,其中不同的值代表着不同的状态,和锁有关的状态有无锁状态、偏向锁、轻量级锁、重量级锁四种,状态的基本转换流程如下:
- 首先,一个对象创建后初始状态是无锁状态
- 当一个线程
t1使用synchronized对一个对象进行加锁时,会对其加偏向锁 - 当线程
t1解锁后,另一个线程t2也要获取锁时,发现对象的Mark Word中存储的线程ID不是当前线程,就会先把锁升级成轻量级锁 - 当线程
t2最终无法获取锁时,锁会升级成重量级锁
偏向锁
偏向锁是Java6引入对锁进行的优化,偏向的意思是偏向某个线程,第一次加锁时把线程ID存到对象的Mark Word中,之后访问时只要先判断和当前的线程ID是不是一致就可以了,只要没有其他线程进行访问,就可以认为这把锁是这一个线程的了,所以主要用于只有一个线程访问的情况下,但是高并发场景下经常是多个线程竞争锁,这也可能是后面被废弃的一个原因。
偏向锁默认是开启的,可以通过
-XX:-UseBiasedLocking来禁止。偏向锁是有延迟的,默认是4秒,可以通过
-XX:BiasedLockingStartupDelay来设置。偏向锁在JDK15中被标记为
Deprecated。调用锁对象的
hashcode方法会使偏向锁失效,因为Mark Word中要存储hashcode,没有空间存储线程id了。
轻量级锁
轻量级锁是相对于重量级锁来说的,因为重量级锁会去操作系统申请Monitor,而轻量级锁的原理是在线程内存中会先创建一条锁记录,然后通过CAS对锁记录地址和Mark Word进行交换,如果交换成功就拿到了锁;如果失败了说明其他线程正在持有锁,可以再进行自旋重试几次,如果持有锁的线程很快执行完了那么就有可能重试获取到锁;如果最终还是失败了,那么锁就会升级成重量级锁。
重量级锁
重量级锁会去系统中申请一个Monitor,其内容主要包括Owner、EntryList、WaitSet三部分:
Owner:拥有者,存储当前持有锁的线程EntryList:没有获取到锁而阻塞的线程列表WaitSet:调用wait方法的线程集合
当线程t1首次加锁时,会把线程标识存到Owner中标识已经有线程加锁,这时当其他线程也要尝试加锁时发现Owner中已经有内容了,就会进入EntryList阻塞,当线程t1执行完就会从EntryList中唤醒阻塞的线程重新竞争锁(这个过程是非公平的,如果有多个线程可能会是任意一个),然后竞争到锁的线程重新进行加锁操作。
如果一个线程获取锁后调用了wait方法,就会进入WaitSet进行阻塞并且会释放锁,只有当其他线程调用对象的notify、notifyAll方法时,就会从WaitSet集合中去唤醒线程重新去竞争锁。