Java 锁相关汇总

  1. synchronized关键字 与 synchronized锁的四种内部状态
  2. java.util.concurrent.lock包下的相关类
  3. 线程安全常见场景及具体编码(线程安全集合等)

sychronized 关键字

背景

应该说的是对象关于锁的四种状态 而不是synchronized的四种状态

JDK1.2 只用重量级锁

jdk1.6之前,synchronized实现是在线程状态“运行”-“阻塞”-“运行”之间切换

线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作。

Java SE1.6为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

-XX:BiasedLockingStartupDelay = 0

默认是4秒多

偏向锁,轻量级锁(自旋锁,无锁)和重量级锁的顺序升级,资源消耗递增,效率递减

锁对比

优点 缺点 适用场景
偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步块场景
轻量级锁(自旋锁,无锁) 竞争的线程不会阻塞,提高了程序的响应速度 如果始终得不到锁竞争的线程使用自旋会消耗CPU 追求响应时间,锁占用时间很短
重量级锁 线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间缓慢 追求吞吐量,锁占用时间较长

自旋锁只是一种锁竞争的方式

对象头和mark word

java的对象由三部分组成:对象头,实例数据,填充字节(对象占用内存是8bit的倍数,不足的需要填充)。
非数组对象的对象头由两部分组成:指向类的指针和Mark Word。
数组对象的对象头由三部分组成,比非数组对象多了块用于记录数组长度。
Mark Word用于记录对象的HashCode和锁信息等,在32位JVM中的Mark Word长度为32bit,
在64位JVM中的Mark Word长度为64bit。
Mark Word的最后2bit是锁标志位,代表当前对象处于哪种锁状态,当Mark Word处于不同的锁状态时,Mark Word记录的信息也有所不同

锁优化的方式

1,减少锁的持有时间
不需要同步执行的代码,不要放在同步代码块中。同步块中代码减少,锁的持续时间短,锁的性能会有所提高。

2,减小锁粒度
把资源分批使用不同的锁,不同批次的资源的操作互不影响。
比如ConturrentHashMap类,把map分成多段,每段一个锁,不在一段的数据可以同时修改。

3,锁分离
把关系不大的操作使用不同的锁,使这些操作互不影响。
比如LinkedBlockingQueue类,从队列头获取数据的take()方法和从队列末尾添加数据的put()方法分别使用不同的锁,两者互不影响。

4,锁粗化
指的是当虚拟机需要连续对同一把锁进行加锁和释放时,尽量改成只使用一次锁。
比如连续多个synchronized语句块,或循环中的synchronized语句块,用的是同一个对象作为锁,可以直接用一个synchronized语句块把他们都包含起来。

5,弃用synchronized关键字
不使用synchronized关键字,可以自己编写代码实现类似偏向锁、自旋锁的功能,减少因为同步锁而带来的效率损耗。比如自己实现的自旋锁。

Link 马士兵

More: 隐式锁

System.out.println()
锁和volatile => as-if-serial