锁是在开发的过程没法避免的问题。也是面试常问的问题。 本章比较详细的解决了java中的锁,记住是锁。html
PS: 下面内容测试的结果不是十分正确。第一,测试的jdk是1.6,而不是1.8.测试的没有关闭UseBiasedLocking(偏向锁)java
多种强度的锁方式。优先使用性能消耗低的方式,当当前方式不可用或者不使用,改变成消耗比当前大的锁方式。 是一种十分灵活,智能的性能优化方案,对非特定场景十分适合。不须要开发者关注锁优化,只须要关注业务程序员
同一个锁, 重入是应付锁复杂使用状况下的一把利器,是锁性能重点。a与b方式使用同一个锁。a调用b,须要两个锁指令,而重入的解决方案是a执行锁指令,当a调用b方法的时候,不使用锁指令。那么锁的开销减小一半。重入得越多锁开销减小越多。 有测试哪里,须要你们本身测试。 下面的连接中性能测试其实已经给了答案,不知道哪位大神,能够把答案告诉打杂的老胡面试
分读写锁,读锁与读锁之间能够共享。在读多写少的场景,提升锁的性能 下面博客有对读写的性能测试:http://www.inter12.org/archives/292spring
为了性能,不按上锁的循序得到锁,即不公平锁。按照上锁的循序得到锁,即公平锁。 公平不是为了优先级 下面博客有对公平的性能测试:https://yq.aliyun.com/articles/48612sql
不用手动调用unLock系列释放锁的方法。解决在复杂的开发体系(业务复杂,开发人员能力良莠不齐,细节无视与混淆,测试困难)中,锁操做问题。 异常释放 谁来释放数据库
当其余线程得到锁以后,等待固定时间以后,尚未得到锁就不在争夺锁。json
synchronized | StampedLock | ReentrantLock | ReentrantReadWriteLock | |
---|---|---|---|---|
锁升级 | 支持 | 支持 | 不支持 | 不支持 |
重入 | 支持 | 不支持 | 可支持 | 可支持 |
读写 | 不支持 | 支持 | 不支持 | 支持 |
公平 | 不支持 | 不支持 | 支持 | 支持 |
自动释放 | 支持 | 不支持 | 不支持 | 不支持 |
锁等待超时 | 不支持 | 支持 | 支持 | 支持 |
线程中断 | 不支持 | 支持 | 支持 | 支持 |
实际使用 | 简单首选 | 不建议使用 | 比较简单 | 当心使用 |
synchronized | StampedLock | ReentrantLock | ReentrantReadWriteLock | 自研 | |
---|---|---|---|---|---|
初级开发人员 | 使用 | 不使用 | 不使用 | 不使用 | 不使用 |
中级开发人员 | 使用 | 不使用 | 不支持 | 不使用 | 不使用 |
高级开发人员 | 使用 | 不使用 | 不支持 | 不使用 | 不使用 |
研发人员 | 使用 | 不使用 | 使用 | 使用 | 使用 |
资深研发人员 | 使用 | 使用 | 使用 | 使用 | 使用 |
synchronized | StampedLock | ReentrantLock | ReentrantReadWriteLock | 自研 | |
---|---|---|---|---|---|
业务系 | 使用 | 不使用 | 不使用 | 不使用 | 不使用 |
功能系 | 使用 | 不使用 | 使用 | 不使用 | 不使用 |
软件系 | 使用 | 不使用 | 使用 | 使用 | 使用 |
业务模块基本不须要关注锁,须要锁的地方都,高级如下程序员都使用的数据库锁,或者其余库锁。打杂的目前在业务系没有用过java锁, 功能模块的开发与维护基本是高级或者研发人员,用到锁的地方很少,绝对大部分是使用的synchronized与ReentrantLock,在spring的代码里面只见到许多synchronized,ReentrantLock还没见过 软件系好比netty,dubbo,大量使用ReentrantLock,一些独立的服务好比rocketmq,核心业务重写了ReentrantLock,还有设计本身的加锁机制性能优化
synchronized | StampedLock | ReentrantLock | ReentrantReadWriteLock | 自研 | |
---|---|---|---|---|---|
读多 | 不使用 | 不使用 | 不使用 | 使用 | 不使用 |
各半 | 使用 | 不使用 | 不使用 | 使用 | 不使用 |
写多 | 使用 | 不使用 | 使用 | 不使用 | 不使用 |
全写 | 使用 | 不使用 | 使用 | 不使用 | 不使用 |
全写操做目前发现最多的就是计数器,计数器建议使用jdk8的LongAdders(计数器),性能超级好。注意任何计数器没法保证绝对的精确性 ** ReentrantLock与ReentrantReadWriteLock的写性能同样**服务器
重入>锁升级>自动释放>锁等待超时>公平>读写>线程中断
在繁多,复杂的方法,代码,逻辑之间相互调用。谁也不知道,哪一个方法,哪段代码使用了锁,一不当心死锁。因此重入是最重要的一点。 除非资深研发人员不然其余人员不该该使用StampedLock
锁升级能够作基本性能方面优化,就交给锁了,可让锁性能在个个场景均可以保持较好的状态,从而减小锁开发与维护的工做量
自动释放对初级,中级或者高级开发来讲,是一个避免出现锁问题的利器,保障开发简单,顺利。不用担忧哪里忘记释放锁,从而形成锁问题
锁等待超时是防止无限锁等待而形成线程资源无限占用与线程池无线程可用的状况,从而让应用没法提供服务。是高可用服务保障的利器
复杂的环境下,不知道哪一个方法,哪一个代码使用了读锁仍是写锁。太多未知与细节,十分头疼,须要大量的时间与精力处理读写关系,得不偿失。
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 ) { // TODO 自动生成的 catch 块 e.printStackTrace(); } }
运行结果
lockMethod: 0 for num0 lockBlock: 1 for num0 lockStaticMethod: 0 for num0 lockBlock: 3 for num1 lockMethod: 2 for num1 lockBlock: 5 for num2 lockStaticMethod: 4 for num1 lockBlock: 7 for num3 lockMethod: 6 for num2 lockBlock: 9 for num4 lockStaticMethod: 8 for num2 lockBlock: 11 for num5 lockMethod: 10 for num3 lockBlock: 13 for num6 lockStaticMethod: 12 for num3 lockBlock: 15 for num7 lockBlock: 17 for num8 lockMethod: 14 for num4 lockBlock: 18 for num9 lockBlock: 20 for num10 lockBlock: 21 for num11 lockStaticMethod: 16 for num4 lockBlock: 22 for num12 lockMethod: 19 for num5 lockBlock: 24 for num13 lockStaticMethod: 23 for num5 lockBlock: 26 for num14 lockMethod: 25 for num6 lockBlock: 28 for num15 lockStaticMethod: 27 for num6 lockBlock: 30 for num16 lockMethod: 29 for num7 lockBlock: 32 for num17 lockStaticMethod: 31 for num7 lockBlock: 34 for num18 lockMethod: 33 for num8 lockBlock: 36 for num19 lockStaticMethod: 35 for num8 lockStaticMethod: 38 for num9 lockStaticMethod: 39 for num10 lockStaticMethod: 40 for num11 lockStaticMethod: 41 for num12 lockStaticMethod: 42 for num13 lockMethod: 37 for num9 lockStaticMethod: 43 for num14 lockMethod: 44 for num10 lockStaticMethod: 45 for num15 lockMethod: 46 for num11 lockStaticMethod: 47 for num16 lockMethod: 48 for num12 lockStaticMethod: 49 for num17 lockMethod: 50 for num13 lockStaticMethod: 51 for num18 lockMethod: 52 for num14 lockStaticMethod: 53 for num19 lockMethod: 54 for num15 lockMethod: 55 for num16 lockMethod: 56 for num17 lockMethod: 57 for num18 lockMethod: 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 ) { // TODO 自动生成的 catch 块 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();//暂停1秒,是为了线程执行不乱 thread2.start( ); sleep(10000);// 暂停10秒,是由于锁方法会展厅5秒,同时为输出内容不会与上面组合乱 System.out.println( "第一个组合 执行结束" ) ; 第一个组合 锁静态方法 块锁锁住this synchronized method static start sleep synchronized block lock this execute synchronized 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( "第二个组合 执行结束" ) ; 第二个组合 锁静态方法 块锁锁住Class synchronized method static start sleep synchronized method static end sleep synchronized 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( "第三个组合 执行结束" ) ; 第三个组合 锁非静态方法 块锁锁住this synchronized method start sleep synchronized method end sleep synchronized 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( "第四个组合 执行结束" ) ; 第四个组合 非锁静态方法 块锁锁住Class synchronized method start sleep synchronized block lock SynchronizedLockMode.class execute synchronized method end sleep 第四个组合 执行结束
经过结果分析会发现第二,三组合的输出结果是有序的。麻烦在看看二,三组合调用的方法 第二个组合是: 锁静态方法 块锁锁住Class 第三个组合是: 锁非静态方法 块锁锁住this
public synchronized static void XXXXX(){ // 锁的对象是 当前的 class }
public synchronized void XXXXX(){ // 锁的对象是 当前的 this }
这是阿里大神fashjson与driud的做者温 对synchronized简单总结
某日,对某个优化过的系统进行,上线前的性能压测,压测结果大大的出乎人意料,优化以后比优化以前的TPS只多200+。在16cpu的服务器不该该出现这样的状况。
是否是接口中数据库操做的问题,MySQL通用日志里记录的sql基本一致,慢日志里面没有记录接口操做的sql。是否是测试人员的测试数据十分重复,更新操做形成锁超时,准备排除锁超时状况,测试人员与业务开发人员反馈,查询接口也同样,数据状态良好 是否是代码问题,分析最后的此时结果,发现全部压测接口都这样。包括简单条主键查询的SQL Why? 奇迹了,数据库与应用一切正常啊。被逼无赖,在每一个核心地方输出调用时间,也没问题。发现全部的接口的使用了RateLimiter的acquire方法,深刻一看,有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.lock( ); wrtieLock.lockInterruptibly( ); readLock.unlock( ); wrtieLock.unlock( ); }
通过测试以后,老胡出了一点冷汗,这个死锁隐藏得太深了。还好是老胡慢慢,慢,看出来了,以老胡的编码方式,还真得出现这样的死锁
在发现死锁现象同一个深夜,老胡在仔细反复的看公布与不公平,读写锁的细节。反复的看调用流程与实现细节,一边准备与周公喝茶了,一个低头砸到桌子上,脑海里整个调用流程与实现细节的流程与逻辑图砸出一个闪光,找到一个问题 在高并发下,不少线程争夺一个锁的时候,在队列的里面的锁可能能难争夺到锁,争夺不到,会饿死啊。
锁方式 | ReentrantLock | ReentrantReadWriteLock |
---|---|---|
lock | 饥饿 | 饥饿 |
lockInterruptibly | 饥饿 | 饥饿 |
tryLock | 不饥饿 | 不饥饿 |
tryLock(超时) | 超时饿醒 | 超时饿醒 |
在高并发状况下,使用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();
至此 2018年01月25日00.26. 历经一个多月,才写完。也真的佩服文字表达能力与技术描述能力,1个多月啊。
欢迎大神蝌蚪,转载。大神蝌蚪的网址是www.kedou.me