跳到主要内容

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 同步代码块的实现是通过 monitorentermonitorexit 指令,其中 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. 自旋锁与自适应自旋锁

自旋的时间(次数)不固定