博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java锁细节整理
阅读量:6245 次
发布时间:2019-06-22

本文共 10617 字,大约阅读时间需要 35 分钟。

一、JDK8存在的锁

  1. synchronized

  2. StampedLock

  3. ReentrantLock

  4. ReentrantReadWriteLock

PS: 下面内容测试的结果不是十分正确。第一,测试的jdk是1.6,而不是1.8.测试的没有关闭UseBiasedLocking(偏向锁)

二、锁的特性

1 锁升级(十二分)
多种强度的锁方式。优先使用性能消耗低的方式,当当前方式不可用或者不使用,改变成消耗比当前大的锁方式。 是一种十分灵活,智能的性能优化方案,对非特定场景十分适合。不需要开发者关注锁优化,只需要关注业务。

2 重入(十二分)

同一个锁, 重入是应付锁复杂使用情况下的一把利器,是锁性能重点。a与b方式使用同一个锁。a调用b,需要两个锁指令,而重入的解决方案是a执行锁指令,当a调用b方法的时候,不使用锁指令。那么锁的开销减少一半。重入得越多锁开销减少越多。 有测试哪里,需要大家自己测试。 下面的链接中性能测试其实已经给了答案,不知道哪位大神,可以把答案告诉打杂的老胡

3 读写(六分)

分读写锁,读锁与读锁之间可以共享。在读多写少的场景,提高锁的性能 下面博客有对读写的性能测试:http://www.inter12.org/archives/292

4 公平

为了性能,不按上锁的循序获得锁,即不公平锁。按照上锁的循序获得锁,即公平锁。 公平不是为了优先级 下面博客有对公平的性能测试:https://yq.aliyun.com/articles/48612

5  自动释放(十二分)

不用手动调用unLock系列释放锁的方法。解决在复杂的开发体系(业务复杂,开发人员能力参差不齐,细节无视与混淆,测试困难)中,锁操作问题。 异常释放,谁来释放。

6. 锁等待(六分)

当其他线程获得锁之后,等待固定时间之后,还没有获得锁就不在争夺锁。

线程中断(一分)

三、开发难度

特性支持
6a9ad5c6bdc264f4fb3585768a90bfbcb556dd2d
开发人员使用分类
3ec82c225e7e0fdde9345185e45b7be07645e474
锁应用环境
a159c2e811cd817323ec37599e37502405e534ce
业务模块基本不需要关注锁,需要锁的地方都应该使用synchronized,高级以下程序员都使用的数据库锁,或者其他库锁。打杂的目前在业务系没有用过java锁, 功能模块的开发与维护基本是高级或者研发人员,用到锁的地方不多,绝对大部分是使用的
synchronized
ReentrantLock
,在
spring
的代码里面只见到许多
synchronized,ReentrantLock
还没见过 软件系比如
netty,dubbo
,大量使用
ReentrantLock
,一些独立的服务比如
rocketmq
,核心业务重写了
ReentrantLock
,还有设计自己的加锁机制
读写类型

业务模块基本不需要关注锁,需要锁的地方都应该使用synchronized,高级以下程序员都使用的数据库锁,或者其他库锁。打杂的目前在业务系没有用过java锁, 功能模块的开发与维护基本是高级或者研发人员,用到锁的地方不多,绝对大部分是使用的synchronizedReentrantLock,在spring的代码里面只见到许多synchronized,ReentrantLock还没见过 软件系比如netty,dubbo,大量使用ReentrantLock,一些独立的服务比如rocketmq,核心业务重写了ReentrantLock,还有设计自己的加锁机制

全写操作目前发现最多的就是计数器,计数器建议使用jdk8的LongAdders(计数器),性能超级好。注意任何计数器无法保证绝对的精确性。

ReentrantLock与ReentrantReadWriteLock的写性能一样。

总结

如果要对特性重要进行排序,要排除对性能极限要求的情况,可以得到以下结论:

重入>锁升级>自动释放>锁等待超时>公平>读写>线程中断

  1. 在繁多,复杂的方法,代码,逻辑之间相互调用。谁也不知道,哪个方法,哪段代码使用了锁,一不小心死锁。所以重入是最重要的一点。 除非资深研发人员否则其他人员不应该使用StampedLock
  2. 锁升级可以做基本性能方面优化,就交给锁了,可以让锁性能在个个场景都可以保持较好的状态,从而减少锁开发与维护的工作量
  3. 自动释放对初级,中级或者高级开发来说,是一个避免出现锁问题的利器,保障开发简单,顺利。不用担心哪里忘记释放锁,从而造成锁问题
  4. 锁等待超时是防止无限锁等待而造成线程资源无限占用与线程池无线程可用的情况,从而让应用无法提供服务。是高可用服务保障的利器
  5. 复杂的环境下,不知道哪个方法,哪个代码使用了读锁还是写锁。太多未知与细节,十分头疼,需要大量的时间与精力处理读写关系,得不偿失。
做了这么多年开发与研发。感觉性能较好的情况下,不出问题与开发维护方便应该放在对性能高度最求的前面。尤其是线上问题,应该避免出现。
从上面的对比分析,synchronized的得分与评价是最高的,ReentrantLock其次, 不建议使用ReentrantReadWriteLock,禁止使用StampedLock。

四、synchronized详析

  1. synchronized 是java关键字,jvm内部实现。所以jvm可以对synchronized进行优化
  2. 每个jdk版本synchronized性能不一样,版本越高的性能越好。jdk1.6与jdk1.7之间的性能差距十分大。
  3. synchronized操作简单,jvm自动优化性能

synchronized详析锁的方式

public class SynchronizedLockMode { private static int increment = 0;  private Object object = new Object( );  public synchronized void lockMethod(){ print("lockMethod"); }  public synchronized static void lockStaticMethod(){ print("lockStaticMethod"); }  public void lockBlock(){ synchronized( object ){ print("lockBlock"); } }}

运行代码:

@Test public void synchronizedLockModeTest(){ SynchronizedLockMode slm = new SynchronizedLockMode(); Thread thread1 = new Thread( new Runnable( ) { public void run ( ) { slm.lockMethod( ); } } ); Thread thread2 = new Thread( new Runnable( ) { public void run ( ) {  SynchronizedLockMode.lockStaticMethod( ); } } ); Thread thread3 = new Thread( new Runnable( ) { public void run ( ) {  slm.lockBlock( ); } } ); thread1.start( ); thread2.start( ); thread3.start( ); try { Thread.sleep( 1000 ); } catch ( InterruptedException e ) { e.printStackTrace(); }}

运行结果:

lockMethod: 0 for num0lockBlock: 1 for num0lockStaticMethod: 0 for num0lockBlock: 3 for num1lockMethod: 2 for num1lockBlock: 5 for num2lockStaticMethod: 4 for num1lockBlock: 7 for num3lockMethod: 6 for num2lockBlock: 9 for num4lockStaticMethod: 8 for num2lockBlock: 11 for num5lockMethod: 10 for num3lockBlock: 13 for num6lockStaticMethod: 12 for num3lockBlock: 15 for num7lockBlock: 17 for num8lockMethod: 14 for num4lockBlock: 18 for num9lockBlock: 20 for num10lockBlock: 21 for num11lockStaticMethod: 16 for num4lockBlock: 22 for num12lockMethod: 19 for num5lockBlock: 24 for num13lockStaticMethod: 23 for num5lockBlock: 26 for num14lockMethod: 25 for num6lockBlock: 28 for num15lockStaticMethod: 27 for num6lockBlock: 30 for num16lockMethod: 29 for num7lockBlock: 32 for num17lockStaticMethod: 31 for num7lockBlock: 34 for num18lockMethod: 33 for num8lockBlock: 36 for num19lockStaticMethod: 35 for num8lockStaticMethod: 38 for num9lockStaticMethod: 39 for num10lockStaticMethod: 40 for num11lockStaticMethod: 41 for num12lockStaticMethod: 42 for num13lockMethod: 37 for num9lockStaticMethod: 43 for num14lockMethod: 44 for num10lockStaticMethod: 45 for num15lockMethod: 46 for num11lockStaticMethod: 47 for num16lockMethod: 48 for num12lockStaticMethod: 49 for num17lockMethod: 50 for num13lockStaticMethod: 51 for num18lockMethod: 52 for num14lockStaticMethod: 53 for num19lockMethod: 54 for num15lockMethod: 55 for num16lockMethod: 56 for num17lockMethod: 57 for num18lockMethod: 58 for num19
 

从上面的执行可以是否发现一个问题,答应是乱序的,自增数据是乱序的。

很多人认为:绝对是java设计的失误...使用一个图片来逻辑推理下:

  1. java与jvm绝对没有错
  2. synchronized是上锁,这点绝对没有问题
  3. 那synchronized锁了什么了?
这是我们讨论的论题,也是一个容易犯错的问题。

演示代码,有四个方法。

public synchronized void lockThisObject(){ sleep("synchronized method"); }  public void VerificationLockMethodIsWhatObject(){ synchronized( this ){ sleep("synchronized block lock this" , false); } }  public synchronized static void lockClassObject(){ sleep("synchronized method static "); }  public void VerificationLockStaticMethodIsWhatObject(){ synchronized( SynchronizedLockMode.class ){ sleep("synchronized block lock SynchronizedLockMode.class" , false); } }  private static void sleep(String lock , boolean boo){ if(boo){ sleep( lock ); }else{ System.out.println( lock + " execute" ) ; } }  private static void sleep(String lock){ sleep0( lock ); }  private static void sleep0(String lock){ try { System.out.println( lock + " start sleep" ) ; Thread.sleep( 10000 ); System.out.println( lock + " end sleep" ) ; } catch ( InterruptedException e ) { e.printStackTrace(); }}

代码解读   

有四个方法分别是静态方法,非静态方法,两个方法里面有synchronized block。四个方法分别组合,测试方法的互斥行。输出内容是按照调用方法的循序执行的,synchronized block方法的输出结果在synchronized 方法之后,那么表示两个方法是互斥的。组合:

  1. 锁静态方法 块锁锁住this;
  2. 锁静态方法 块锁锁住Class;
  3. 锁非静态方法 块锁锁住this ;
  4. 锁非静态方法 块锁锁住Class。

test代码与结果 第一个组合

SynchronizedLockMode slm = new SynchronizedLockMode();Thread thread1 = new Thread( new Runnable( ) { public void run ( ) { SynchronizedLockMode.lockClassObject( ); }} );Thread thread2 = new Thread( new Runnable( ) { public void run ( ) { slm.verificationLockMethodIsWhatObject( ); } } );System.out.println( "第一个组合 锁静态方法 块锁锁住this" ) ;thread1.start( );sleep();thread2.start( );sleep(10000);System.out.println( "第一个组合 执行结束" ) ;第一个组合 锁静态方法 块锁锁住thissynchronized method static start sleepsynchronized block lock this executesynchronized method static end sleep第一个组合 执行结束

第二个组合:

thread1 = new Thread( new Runnable( ) { public void run ( ) { SynchronizedLockMode.lockClassObject( ); }} );thread2 = new Thread( new Runnable( ) { public void run ( ) { slm.verificationLockStaticMethodIsWhatObject( ); }} );System.out.println( "第二个组合 锁静态方法 块锁锁住Class" ) ;thread1.start( );sleep();thread2.start( );sleep(10000);System.out.println( "第二个组合 执行结束" ) ;第二个组合 锁静态方法 块锁锁住Classsynchronized method static start sleepsynchronized method static end sleepsynchronized block lock SynchronizedLockMode.class execute第二个组合 执行结束

第三个组合:

thread1 = new Thread( new Runnable( ) { public void run ( ) { slm.lockThisObject( ); }} );thread2 = new Thread( new Runnable( ) { public void run ( ) { slm.verificationLockMethodIsWhatObject( ); }} );System.out.println( "第三个组合 锁非静态方法 块锁锁住this" ) ;thread1.start( );sleep();thread2.start( );sleep(10000);System.out.println( "第三个组合 执行结束" ) ;第三个组合 锁非静态方法 块锁锁住thissynchronized method start sleepsynchronized method end sleepsynchronized block lock this execute第三个组合 执行结束

第四个组合

thread1 = new Thread( new Runnable( ) { public void run ( ) { slm.lockThisObject( ); } } );thread2 = new Thread( new Runnable( ) { public void run ( ) { slm.verificationLockStaticMethodIsWhatObject( ); } } ); System.out.println( "第四个组合 非锁静态方法 块锁锁住Class" ) ; thread1.start( ); sleep(); thread2.start( ); sleep(10000); System.out.println( "第四个组合 执行结束" ) ;第四个组合 非锁静态方法 块锁锁住Classsynchronized method start sleepsynchronized block lock SynchronizedLockMode.class executesynchronized method end sleep第四个组合 执行结束

通过结果分析会发现第二,三组合的输出结果是有序的。麻烦在看看二,三组合调用的方法 第二个组合是: 锁静态方法 块锁锁住Class 第三个组合是: 锁非静态方法 块锁锁住this

结论

synchronized关键字标记在静态方法上是锁当前的class对象。

public synchronized static void XXXXX(){ // 锁的对象是 当前的 class}

synchronized关键字标记在非静态方法上是锁当前的实例对象(this)

public synchronized void XXXXX(){ // 锁的对象是 当前的 this}

总结

jdk版本越高synchronized的性能越好
这是阿里大神fashjson与driud的作者温 对synchronized简单总结

06cef989e06e24518bd91df6890305f8ccb4b8d8

背景:

某日,对某个优化过的系统进行,上线前的性能压测,压测结果大大的出乎人意料,优化之后比优化之前的TPS只多200+。在16cpu的服务器不应该出现这样的情况。

问题排查:

是不是接口中数据库操作的问题,MySQL通用日志里记录的sql基本一致,慢日志里面没有记录接口操作的sql。是不是测试人员的测试数据十分重复,更新操作造成锁超时,准备排除锁超时情况,测试人员与业务开发人员反馈,查询接口也一样,数据状态良好   是不是代码问题,分析最后的此时结果,发现所有压测接口都这样。包括简单条主键查询的SQL   Why? 奇迹了,数据库与应用一切正常啊。被逼无赖,在每个核心地方输出调用时间,也没问题。发现所有的接口的使用了RateLimiter的acquire方法,深入一看,有synchronized。每次接口的调用都会进入下面的代码,而每次都会有锁争夺。

5abed8508a1c406bab6b6492e59c414ff8039593

解决方案:

高并发下synchronize造成jvm性能消耗详析

jvm对synchronized代码块的优化

google guava 的RateLimiter 限流的核心计算代码使用的synchronized,google大神都证明了synchronized的优秀

公平锁与不公平锁:

  1. 在一般竞争情况下,两者的性能可以理解为相等
  2. 在极高竞争下,不公平锁的性能是可能是公平锁的十几倍

ReentrantReadWriteLock 死锁现象

背景

某个深夜,老胡在看ReentrantReadWriteLock源码,想用ReentrantReadWriteLock代替ReentrantLock提高性能,反复的看调用流程与实现细节(看了两个多小时),脑海慢慢呈现整个调用流程与实现细节的流程与逻辑图,发现不对劲啊,可能一不小心出现死锁public void reentrantReadWriteLock() throws InterruptedException{ ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock(); ReadLock readLock = rrwl.readLock( ); WriteLock wrtieLock = rrwl.writeLock( ); readLock.lock( ); readLock.lock( ); readLock.unlock( ); readLock.unlock( ); wrtieLock.lock( ); wrtieLock.lock( ); wrtieLock.unlock( ); wrtieLock.unlock( ); wrtieLock.lock( ); readLock.lock( ); wrtieLock.unlock( ); readLock.unlock( ); readLock.lock( ); wrtieLock.lockInterruptibly( ); readLock.unlock( ); wrtieLock.unlock( ); }经过测试之后,老胡出了一点冷汗,这个死锁隐藏得太深了。还好是老胡慢慢,慢,看出来了,以老胡的编码方式,还真得出现这样的死锁

ReentrantLock 与 ReentrantReadWriteLock 在高并发下的不公平锁 出现饿死现象:

在发现死锁现象同一个深夜,老胡在仔细反复的看公布与不公平,读写锁的细节。反复的看调用流程与实现细节,一边准备与周公喝茶了,一个低头砸到桌子上,脑海里整个调用流程与实现细节的流程与逻辑图砸出一个闪光,找到一个问题在高并发下,很多线程争夺一个锁的时候,在队列的里面的锁可能能难争夺到锁,争夺不到,会饿死啊。
3adf89cba76c3588da38adf979778241e505bb03

锁使用总结:在高并发情况下,使用tryLock(超时)杜绝 饥饿。没获得锁,可以直接异常与返回异常结果

  1. 能不使用锁,绝对不要使用...........
  2. 注意上面说的细节,比如synchronized锁的对象等
  3. 优先使用synchronized关键字,能不用使用synchronized块就不使用
  4. 高并发的情况下使用synchronized,麻烦关闭偏向锁-XX:-UseBiasedLocking
  5. 减少锁粒度
277f84b922e1173b52981827ce317c7d11e99a5d
  1. 优先使用重入锁,禁止非重入锁StampedLock的使用
  2. 一定要分析场景,在选择对应的锁,如果不分析只能使用synchronized
  3. tryLock(超时) 是处理死锁与饥饿的神器。
  4. 一个class或者一个实例里面只允许一个锁。两个锁容易出现死锁。这个锁必须能重入
private Object object = new Object();public void a(){ synchronized(object){ }}public void b(){ synchronized(object){ }}// c 方法与a,b方法的锁不是一个,在这个类里面有两个锁分别是 object与this,public void c(){ synchronized(this){ }}// 同时存在 a b锁,很容易不小心死锁。ReentrantLock a= new ReentrantLock();ReentrantLock b= new ReentrantLock();
原文发布时间为:2018-02-2
本文作者:鸟菜啊
本文来自云栖社区合作伙伴“ ”,了解相关信息可以关注“ ”微信公众号

转载地址:http://oelia.baihongyu.com/

你可能感兴趣的文章
#iOS问题记录# UITextview富文本链接,禁止长按事件
查看>>
深度网络实现手写体识别
查看>>
Python Module_subprocess_调用 Powershell
查看>>
MVC原理图解
查看>>
c基础
查看>>
nodejs 平台的 webscoket 的实现
查看>>
JDK1.8源码(三)——java.util.HashMap
查看>>
给你1000万你可以把生活过的更好吗?
查看>>
<jsp:include page>和<%@ include file%>的区别
查看>>
flash 类和对象的关系
查看>>
保护模式 宏观理解
查看>>
Hat’s Words
查看>>
has_many :through VS has_and_belongs_to_many
查看>>
比较JSF、Spring MVC、Stripes、Struts 2、Tapestry、Wicket
查看>>
正则表达式介绍及案例分享
查看>>
【BZOJ】2125: 最短路 圆方树(静态仙人掌)
查看>>
【BZOJ】4530: [Bjoi2014]大融合
查看>>
线代之高斯消元
查看>>
java-循环的应用环境以及数组的创建
查看>>
关于java@Override错误
查看>>