Java 关键字 Synchronized 与锁优化机制

与 ReentrantLock 区别

ReentrantLock 独有能力

  • 类库层面的同步

  • 等待可中断,持有锁的线程长期不释放锁的时候,等待的线程可以放弃等待。

  • 可实现公平锁 ,按照申请锁的时间顺序获取锁,不过公平锁讲导致其性能的急速下降,明显影响吞吐量。

  • 锁绑定多个条件,一个 ReentrantLock 可以同时绑定多个 Condition 对象,而 synchronized 与 notifyAll 配合之恶能实现一个隐含的条件

为什么保留 synchronized

  • synchronized 是 Java 语法层面的同步,简单清晰
  • Lock 需要主动释放锁
  • JVM 在线程和对象元数据中记录了 synchronized 锁的相关信息,而 Lock 没有

底层实现

  • 当方法内部使用 synchronized ,monitorenter 尝试获取对象的锁,monitorexit 用于释放锁;

第二个 monitorexit 用于处理程序可能发生的异常,由编译器自动生成,在发生异常时处理异常然后释放掉锁。

  • 当 synchronized 修饰方法块时,是根据 ACC_SYNCHRONIZED 标志位去控制同步逻辑
public class Test3 {
    public void methodA(){
        synchronized (Test3.class){
            System.out.println(123);
        }
    }
    public static void main(String[] args) {
    }
}

对应字节码

  public void methodA();
    Code:
       0: ldc           #2                  // class cn/com/egova/base/canal/handler/Test3
       2: dup
       3: astore_1
       4: monitorenter
       5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: bipush        123
      10: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
      13: aload_1
      14: monitorexit
      15: goto          23
      18: astore_2
      19: aload_1
      20: monitorexit
      21: aload_2
      22: athrow
      23: return
    Exception table:
       from    to  target type
           5    15    18   any
          18    21    18   any

对方法加锁时,会有 ACC_SYNCHRONIZED 标志

public class Test4 {
    public synchronized void test(){
        System.out.println(123);
    }
    public static void main(String[] args) {
    }
}

public synchronized void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: bipush        123
         5: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
         8: return
      LineNumberTable:
        line 10: 0
        line 11: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcn/com/egova/base/canal/handler/Test4;

锁优化

  • 自旋锁与自适应自旋

Java 的线程时映射到操作系统的原生内核线程上,挂起线程和恢复线程需要转到内核态进行,比较消耗资源;所以 JVM 会让线程执行一个忙等待(自旋),然后再获取锁。

JDK 6 对自旋锁进行了优化,等待的时间根据同一个锁上面的自旋时间和锁的拥有者的状态来决定。

若上一次自旋刚获取成功过锁,那么 JVM 允许线程等待相对更长的时间;若自旋很少成功获得锁,那么会直接省略自旋的过程

  • 锁消除

JIT 即时编译器通过逃逸分析技术,发现 synchronized 锁对象,只有一个线程能加锁,不存在共享数据竞争的问题,那么会将锁进行消除。

例如,下面代码在字节码层面会转换为 StringBuilder.append 方式,而 append 方法都有一个同步块,JVM 分析到参数只会在方法内部调用,不存在共享数据的竞争,这里的锁可以被消除掉。

public String concatString(String s1, String s2, String s3) { 
	return s1 + s2 + s3; 
}
  • 锁粗化

若一系列的连续操作都是对同一个对象反复加锁和解锁,甚至加锁操作在循环体中,JVM 会将加锁同步的范围扩展(粗化)到操作外部。

  • 轻量级锁

相对于 synchronized 重量级锁而言,它的设计初衷,就是在没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能消耗。

实现:在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象 Mark Word 的拷贝;然后 JVM 通过 CAS 操作,将锁对象的 Mark Word 更新为指向 Lock Record 的指针,CAS 成功表示成功获取到锁,Mark Word 中的锁标志位也改为 01

若 CAS 更新失败,说明当前存在竞争,然后 JVM 检查锁对象的 Mark Word 是否指向当前线程的栈帧,如果是,那么说明线程已经拥有这个对象的锁,直接进入同步方法块;否则轻量级锁膨胀为重量级锁,锁标志位改为 10。
在这里插入图片描述

  • 偏向锁

意思是锁会偏向于第一个获得它的线程,在后面执行过程中,锁没有被其它线程获取,那么持有偏向锁的线程将无需再进行同步。

偏向锁可以提高带有同步但无竞争的程序性能,但是对于程序中大多数锁总是被不同线程访问的情况,偏向锁就是多余的。

轻量级锁是在无竞争条件下使用 CAS 操作去消除同步带来的互斥量;偏向锁是在无竞争的情况下,把整个同步都消除,连 CAS 操作都不做。

锁升级

synchronized 锁升级过程:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值