java加锁
A. Synchronized原理
synchronized是Java中用于加锁的关键字,它允许为对象和方法,以及代码块加锁。当synchronized用于锁定一个方法或代码块时,同一时刻最多只有一个线程能够执行这段代码。如果另一个线程试图访问加锁的代码块,它必须等待当前线程执行完该代码块后才能执行。然而,当一个线程访问加锁的对象的代码块时,另一个线程仍可以访问该对象的非加锁代码块。
synchronized有三种主要的应用方式:
修饰实例方法:这会在当前实例上加锁,确保在执行同步代码前获取实例锁。
修饰静态方法:这会在当前类的对象(即Class对象)上加锁,进入同步代码前要获取Class对象的锁。
修饰代码块:这允许指定锁定的对象,对给定对象加锁,确保在进入同步代码块前获取对象的锁。
Java对象头包括两部分:Mark Word 和 Class Metadata Address,用于存储对象的运行时数据,如哈希码、GC分代年龄、锁状态标志等。在32位虚拟机中,Mark Word的32个Bits中用于存储对象的哈希码、分代年龄、锁标志等信息。
Monitor对象在Java中是一个同步工具,它负责管理对象的锁状态。每个Java对象都关联着一个Monitor,它在对象创建时生成,用于控制线程对对象的访问。Monitor由ObjectMonitor实现,包含两个队列:_WaitSet 和 _EntryList,分别用于保存等待锁的线程列表和持有锁的线程列表。当线程试图获取锁时,会进入 _EntryList;获取锁后,线程进入 _Owner 区域并设置Monitor的owner变量。当线程调用wait()方法时,它会释放Monitor锁,让出机会给其他线程。
Synchronized关键字在字节码层面通过monitorenter和monitorexit指令实现同步代码块和同步方法的加锁与解锁。在方法调用时,JVM会检查方法是否为同步方法,如果是,则线程会先获取Monitor锁,然后执行方法。当方法执行完毕时,无论以何种方式结束,都会释放Monitor锁。Java SE1.6引入了偏向锁、轻量级锁和重量级锁来减少锁操作的开销。偏向锁和轻量级锁是乐观锁,它们在多个线程竞争时才升级为重量级锁,后者依赖于操作系统互斥量实现,开销较大。
自旋锁是一种在获取锁失败时,让线程在当前CPU上进行循环等待的优化策略,以避免线程切换的开销。自适应自旋锁则根据前一次在同一个锁上的自旋情况,调整自旋次数,以提高效率。
锁消除是JVM在逃逸分析的基础上,识别出不存在共享数据竞争的代码块,从而移除不必要的同步锁,以节省资源。
锁的膨胀流程大致为:偏向锁 -> 轻量级锁 -> 重量级锁。偏向锁是为单线程优化的,轻量级锁在多线程竞争不激烈时使用,当竞争加剧时升级为重量级锁。重量级锁使用操作系统互斥量实现,成本较高。自旋锁和自适应自旋锁则是在获取锁失败时,通过在当前CPU上循环等待,减少线程切换的开销。