Java锁细节整理

前言

锁是在开发的过程没法避免的问题。也是面试常问的问题。 本章比较详细的解决了java中的锁,记住是锁。html

一. JDK8存在的锁

  1. synchronized
  2. StampedLock
  3. ReentrantLock
  4. ReentrantReadWriteLock

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

二,锁的特性

  1. 锁升级(十二分)

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

  1. 重入(十二分)

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

  1. 读写(六分)

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

  1. 公平

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

  1. 自动释放(十二分)

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

  1. 锁等待(六分)

当其余线程得到锁以后,等待固定时间以后,尚未得到锁就不在争夺锁。json

  1. 线程中断(一分)

三,开发难度

特性支持
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的写性能同样**服务器

总结

若是对特性重要进行排序的话我认为是 非对性能极限要求的

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

  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 ) {
			// 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设计的失误....... //???使用一个图片

来逻辑推理下:

  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 ) {
			// 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

结论:

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

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

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

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

总结

jdk版本越高synchronized的性能越好

这是阿里大神fashjson与driud的做者温 对synchronized简单总结 1

背景

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

问题排查

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

2

解决方案

高并发下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.lock( );
		wrtieLock.lockInterruptibly( );
		readLock.unlock( );
		wrtieLock.unlock( );
	}
通过测试以后,老胡出了一点冷汗,这个死锁隐藏得太深了。还好是老胡慢慢,慢,看出来了,以老胡的编码方式,还真得出现这样的死锁

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

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

在高并发状况下,使用tryLock(超时)杜绝 饥饿。没得到锁,能够直接异常与返回异常结果

锁使用总结

  1. 能不使用锁,绝对不要使用...........
  2. 注意上面说的细节,好比synchronized锁的对象等
  3. 优先使用synchronized关键字,能不用使用synchronized块就不使用
  4. 高并发的状况下使用synchronized,麻烦关闭偏向锁-XX:-UseBiasedLocking
  5. 减小锁粒度 2
  6. 优先使用重入锁,禁止非重入锁StampedLock的使用
  7. 必定要分析场景,在选择对应的锁,若是不分析只能使用synchronized
  8. tryLock(超时) 是处理死锁与饥饿的神器。
  9. 一个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年01月25日00.26. 历经一个多月,才写完。也真的佩服文字表达能力与技术描述能力,1个多月啊。

欢迎大神蝌蚪,转载。大神蝌蚪的网址是www.kedou.me

相关文章
相关标签/搜索