简介
Java 中内置关键字,实现线程同步,支持可重入的独占锁。
特性
- 原子性:确保线程互斥访问同步代码。
- 可见性:保证共享变量修改及时可见。lock操作会清空工作空间变量值;执行unlock前,变量同步主内存。
- 有序性:synchronized内代码和外部代码禁止指令重排,内部代码不会禁止重排。
- 独占锁:一次只能被一个线程持有,其他线程阻塞。
- 非公平锁:线程直接尝试获取锁,不判断是有有阻塞线程。
- 可重入锁
三种使用方式
- 代码块
Object obj = new Object();
public void test(){
synchronized (obj){
// ....
}
}
- 方法
public synchronized void test(){
}
- 静态方法
public synchronized static void test(){
}
这三种有什么区别,到底锁的是什么?
对象
这不跟没说一样……
本质上就是锁的是对象,代码块是obj对象,方法其实就是实例对象,就是this,这两种只有是同一个实例才会存在锁竞争;静态方法就是类对象,只要有一个线程获取到锁,其他线程就无法访问。
实现原理
方法和对象加锁编译后字节码
底层原理是基于JVM的指令和对象的监视器(monitor)来实现的。
线程访问被synchronized修改时方法或代码块,先获取所属对象监视器,获取成功,执行代码块,监视器计数+1,如果失败,就会阻塞,直到监视器被释放。
synchronized修饰方法时,ACC_SYNCHRONIZED标识方法是同步的;
synchronized修饰代码块时,monitorenter和monitorexit两个指令用来进入和退出监视器;
具体实现
在HotSpot虚拟机中,Java 对象在内存布局中大体分:对象头、实例数据和对其填充;
而普通对象头由Mark Word、Klass point;Mark Word占8字节,对象头布局如下:
OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
后三位表示锁 第一位:无锁:0,偏向锁:1 后两位:无锁:01,偏向锁:01,轻量级锁:00,重量级锁:10,gc标记:11
无锁(偏向锁:0,锁标志位:01)
偏向锁(偏向锁:1,锁标志位:01,线程ID)
- 偏向锁标志位是否是1,锁标志位是否为01,确认偏向状态
- 如果为可偏向状态,是否为当前线程,如果是,进入同步操作,否则进入一步
- CAS获取偏向锁失败,标识资源被占。当到达安全点时,偏向锁升级为轻量级锁,然后被阻塞在安全点继续执行同步代码
- CAS获取偏向锁失败,标识资源被占。当到达安全点时,偏向锁升级为轻量级锁,然后被阻塞在安全点继续执行同步代码
轻量级锁(锁标志位:00)
- 代码进入同步块,如果对象锁为偏向状态通过CAS抢占,锁标志位为00
- 抢到,进入同步
- 未抢到,循环执行,循环一定阶段,升级重量级锁
重量级锁(锁标志位:10)
- 分配ObjectMonitor对象,锁标志位设为10,mark word存储指向ObjectMonitor
- ObjectMonitor有两个队列,用来保存ObjectWaiter对象列表,多个线程访问同一段代码,首先进入EntryList
- 通过CAS尝试将Monitor owner修改为当前线程,count+1,发现owner是当前线程recursions+1,CAS失败,进入EntryList中
- 当获取锁线程调用wait方法,owner设置null,count-1,recursions-1,当前线程加入waitSet,等待被唤醒
- 线程执行完代码块时,释放锁,count-1,recursions-1,recursions为0,说明线程释放锁
做了哪些优化
-
锁膨胀
锁膨胀:无锁 到 偏向锁 ,再到轻量级锁,最后变成重量级锁。也叫锁升级(不可降级)
JDK1.6之前,synchronized是重量级锁,释放、获取锁都会从用户态到内核态转换,效率低,引入锁膨胀,偏向锁、轻量级锁在并发操作不用用户态、内核态转换,提交性能。
-
锁粗化
将多个连续的加锁、解锁连接在一起变成范围更大的锁。
-
锁消除
某些情况下,虚拟机检测不到某段代码存在竞争的可能,将代码同步消除掉,从而提交程序性能。
-
自旋锁
通过自身循环,尝试获取锁;优点:避免一些线程挂起、恢复操作,如果循环次数太多,浪费一定资源,需要设置阈值。
面试知识点
-
什么是synchronized
-
什么是可重入锁,synchronized如何实现的
当线程持有锁,当前线程可以多次进入被锁定的代码块或方法而不会被阻塞;
占有锁后对象头中会有线程ID,可重入锁,每次进入+1,退出-1,直至状态为0,释放锁
-
什么是锁升级
同锁膨胀
-
synchronized是非公平还是公平的
非公平
-
synchronized如何保证线程安全的
- 原子性
- 有序性
- 可见性
-
如何提升synchronized性能
本质上减少锁竞争
尽量保证锁处于轻量级锁或偏向锁
减小锁粒度
减小锁持有时间
-
synchronized和ReentrantLock区别
相同
都是可重入锁,可实现线程同步
区别
- synchronized是关键字,依赖jvm;ReentrantLock是通过AQS编程实现
- ReentrantLock更加灵活,功能丰富,等待可中断、可实现公平锁
这是一个从 https://juejin.cn/post/7369248993106280500 下的原始话题分离的讨论话题