前言
本文主要内容
- 介绍Synchronized并进行简单使用
- 简要介绍Synchronized的实现原理
- 介绍锁的升降级
正文
介绍及简单使用
介绍
重量级锁
因为是互斥同步方式,同一时间只能有一个线程操作共享资源,又因为使用的调度方式是抢占式调度,因此在时间片结束前,正在执行的线程无法将任务执行完。当时间到达,线程挂起,等到下次抢占后,恢复现场,继续工作。一个任务的完成需要频繁的挂起与恢复,而挂起与恢复需要转入内核态完成,这就会在用户态与内核态之间频繁转换,而这种转换会占用大量资源。因此这种实现单一线程操作共享资源的锁被称为重量级锁。在jdk1.6之前,synchronized只有这种方式。
JDK1.6时的优化
首先,重量级锁实现方式并不是不好,在高并发的环境下,这种方式不可替代,但是需要分情况,若竞争同一个共享资源线程数量小于cpu核心数,也就是小于硬件支持的并行线程数量,那么是不是可以考虑下,不让等待的线程挂起?只是让其进行无意义的活动,进行等待;若在某一个时间段,同一个线程连续操作同一个共享资源,那么是否可以考虑在这一时间段取消同步?当作这一个线程独享的moment,直到一个不同的线程开始竞争;对于一个虽然编码阶段是同步资源,但判定它不可能有共享的机会,那么是否应该取消同步?
针对这三个特殊情况,JDK1.6进行了优化,因此有了轻量级锁,偏向锁,无锁
简单使用
内部锁是在对象上加锁。可以修饰静态方法、非静态方法、代码块
public class SynchronizedTest {
private static SynchronizedTest instance;
private int num;
//修饰静态方法,锁类对象
public static synchronized SynchronizedTest getInstance1(){
if(null == instance){
instance = new SynchronizedTest();
}
return instance;
}
//修饰代码块,锁指定对象
public static SynchronizedTest getInstance2(){
if(null == instance){
synchronized (SynchronizedTest.class){
if(null == instance){
instance = new SynchronizedTest();
}
}
}
return instance;
}
//修饰非静态方法,锁当前对象(这个代码纯粹是为了演示)
public synchronized void add(int num){
num++;
}
}
实现原理
预备知识
对象头
ObjectMonitor
每个对象都有一个与之关联的Monitor对象 -一个synchronized跟面试官扯了半个小时
OpenJDK / jdk8u / jdk8u / hotspot
这里需要关注的变量有:
- _count: 重入次数
- _waiters: 等待线程数
- _owner: 当前持有锁的线程
- _WaitSet: 调用了 wait 方法被阻塞的线程
- _EntryList: 等待获取锁的线程
加锁与释放锁的实现
重量级锁的实现
共享资源对应着一个monitor对象,多个线程竞争共享资源,其实是竞争monitor。请求锁的线程先存入竞争队列中,当线程获取monitor后,
_owner指向当前线程,对象头中的标记字段中记录monitor地址,同时设置锁标志位为10
偏向锁的实现
当锁对象第一次被线程获取时,对象头的标志位记录当前线程ID,若设置成功,则取消同步,直到有其它线程竞争时,取消偏向锁
轻量级锁的实现
若线程数量不超过硬件支持的并行数量,那么其余线程未抢到锁的线程,可以进行自旋来忙等待,以避免频繁的上下文切换。线程会在自己的栈帧中创建锁记录,然后拷贝资源对象的Mark Word,然后将锁记录中的owner指向
在过程中会进行自旋,当超过旋转次数时,就会进行锁升级,升级为重量级锁,避免浪费cpu资源,如果同一个锁上一次成功自旋获取了,那么对于这个锁,下一次自旋的次数就会增加,如失败则相反,这就是自适应自旋