synchronized 关键字
synchronized 的作用
synchronized 可以保证被它修饰的方法或代码块在任意时刻只能有一个线程执行。
synchronized 和 volatile 的区别是什么?
volatile
解决的是内存可见性
问题,会使得所有对 volatile
变量的读写都直接写入主存,即 保证了变量的可见性。
synchronized
解决的是执行控制
的问题,它会阻止其他线程获取当前对象的监控锁。
synchronized 使用
对当前实例对象加锁
下面的两个方法等效
public synchronized void test(){
}
public void test1(){
synchronized (this){
}
}
对这个类的所有对象加锁
下面的两个方法等效
public synchronized static void test2(){
}
public static void test3(){
synchronized (SynchronizedClient.class){
}
}
说一下 synchronized 底层实现原理?
synchronized 同步代码块的实现是通过 monitorenter
和 monitorexit
指令,其中 monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置。当执行 monitorenter
指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。
其内部包含一个计数器,当计数器为 0 则可以成功获取,获取后将锁计数器设为 1 也就是加 1。相应的在执行 monitorexit
指令后,将锁计数器设为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止
多线程中 synchronized 锁升级的原理是什么?
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid
字段,在第一次访问的时候 threadid
为空,jvm
让其持有偏向锁,并将 threadid
设置为其线程 id
,再次进入的时候会先判断 threadid
是否与其线程 id
一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized
锁的升级。
JVM 对 synchronized 的优化有哪些?
1. 锁膨胀
无锁状态下,线程 A 获取锁,锁的状态会升级为偏向锁。 此时,线程 B 尝试获取锁,锁升级为轻量级锁。 如果线程 B 获取锁失败,线程 B 自旋等待,自旋一定次数之后,锁升级为重量级锁。 如果多个线程同时获取锁,锁升级为重量级锁。
2.锁消除
消除锁是虚拟机另外一种锁的优化,这种优化更彻底,在JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁。
public static String test() {
Object obj = new Object();
synchronized (obj) {
return obj.toString();
}
}
3. 锁粗化
锁粗化是虚拟机对另一种极端情况的优化处理,通过扩大锁的范围,避免反复加锁和释放锁。
Object obj = new Object();
public static String test() {
for(int i = 0; i < 100; i++) {
synchronized (obj) {
// do something....
}
}
}
Object obj = new Object();
public static String test() {
synchronized (obj) {
for(int i = 0; i < 100; i++) {
// do something....
}
}
}
4. 自旋锁与自适应自旋锁
自旋的时间(次数)不固定