本文共 10617 字,大约阅读时间需要 35 分钟。
synchronized
StampedLock
ReentrantLock
ReentrantReadWriteLock
PS: 下面内容测试的结果不是十分正确。第一,测试的jdk是1.6,而不是1.8.测试的没有关闭UseBiasedLocking(偏向锁)
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. 锁等待(六分)
当其他线程获得锁之后,等待固定时间之后,还没有获得锁就不在争夺锁。
线程中断(一分)业务模块基本不需要关注锁,需要锁的地方都应该使用synchronized,高级以下程序员都使用的数据库锁,或者其他库锁。打杂的目前在业务系没有用过java锁, 功能模块的开发与维护基本是高级或者研发人员,用到锁的地方不多,绝对大部分是使用的synchronized与ReentrantLock,在spring的代码里面只见到许多synchronized,ReentrantLock还没见过 软件系比如netty,dubbo,大量使用ReentrantLock,一些独立的服务比如rocketmq,核心业务重写了ReentrantLock,还有设计自己的加锁机制
全写操作目前发现最多的就是计数器,计数器建议使用jdk8的LongAdders(计数器),性能超级好。注意任何计数器无法保证绝对的精确性。
ReentrantLock与ReentrantReadWriteLock的写性能一样。
重入>锁升级>自动释放>锁等待超时>公平>读写>线程中断
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设计的失误...使用一个图片来逻辑推理下:
演示代码,有四个方法。
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 方法之后,那么表示两个方法是互斥的。组合:
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
public synchronized static void XXXXX(){ // 锁的对象是 当前的 class}
public synchronized void XXXXX(){ // 锁的对象是 当前的 this}
背景:
某日,对某个优化过的系统进行,上线前的性能压测,压测结果大大的出乎人意料,优化之后比优化之前的TPS只多200+。在16cpu的服务器不应该出现这样的情况。
是不是接口中数据库操作的问题,MySQL通用日志里记录的sql基本一致,慢日志里面没有记录接口操作的sql。是不是测试人员的测试数据十分重复,更新操作造成锁超时,准备排除锁超时情况,测试人员与业务开发人员反馈,查询接口也一样,数据状态良好 是不是代码问题,分析最后的此时结果,发现所有压测接口都这样。包括简单条主键查询的SQL Why? 奇迹了,数据库与应用一切正常啊。被逼无赖,在每个核心地方输出调用时间,也没问题。发现所有的接口的使用了RateLimiter的acquire方法,深入一看,有synchronized。每次接口的调用都会进入下面的代码,而每次都会有锁争夺。
高并发下synchronize造成jvm性能消耗详析
jvm对synchronized代码块的优化
google guava 的RateLimiter 限流的核心计算代码使用的synchronized,google大神都证明了synchronized的优秀
某个深夜,老胡在看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( ); }经过测试之后,老胡出了一点冷汗,这个死锁隐藏得太深了。还好是老胡慢慢,慢,看出来了,以老胡的编码方式,还真得出现这样的死锁
在发现死锁现象同一个深夜,老胡在仔细反复的看公布与不公平,读写锁的细节。反复的看调用流程与实现细节,一边准备与周公喝茶了,一个低头砸到桌子上,脑海里整个调用流程与实现细节的流程与逻辑图砸出一个闪光,找到一个问题在高并发下,很多线程争夺一个锁的时候,在队列的里面的锁可能能难争夺到锁,争夺不到,会饿死啊。
锁使用总结:在高并发情况下,使用tryLock(超时)杜绝 饥饿。没获得锁,可以直接异常与返回异常结果
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();
转载地址:http://oelia.baihongyu.com/