转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/120750932
本文出自【赵彦军的博客】
Java线程安全StampedLock
Java线程安全Lock、ReentrantLock、ReentrantReadWriteLock
Java线程安全集合总结
Java原子操作Atomic
前言
java5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁的功能,它提供了与synchronized关键字类似的同步功能。
既然有了synchronized这种内置的锁功能,为何要新增Lock接口?先来想象一个场景:手把手的进行锁获取和释放,先获得锁A,然后再获取锁B,当获取锁B后释放锁A同时获取锁C,当锁C获取后,再释放锁B同时获取锁D,以此类推,这种场景下,synchronized关键字就不那么容易实现了,而使用Lock却显得容易许多。
Lock
Lock 是一个接口
publicinterfaceLock{voidlock();voidlockInterruptibly()throwsInterruptedException;booleantryLock();booleantryLock(long time,TimeUnit unit)throwsInterruptedException;voidunlock();ConditionnewCondition();}
ReentrantLock
publicclassReentrantLockimplementsLock,java.io.Serializable{}
使用如下:
publicclassLockTest{Lock lock=newReentrantLock();publicvoidrun(){//获取锁
lock.lock();//doSomething//释放锁
lock.unlock();}}
使用起来,也非常简单,首先lock.lock();
获取锁,然后去执行自己的逻辑,最后调用lock.unlock();
释放锁。
但是这里有个问题,如果我们的逻辑发生异常,那就永远无法释放锁,所以我们优化一下,把释放锁的逻辑放在finally
块中,如下
publicclassLockTest{Lock lock=newReentrantLock();publicvoidrun(){//获取锁
lock.lock();try{//doSomething}catch(Exception e){}finally{//释放锁
lock.unlock();}}}
公平锁/非公平锁
非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;
公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。
ReentrantLock
主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似。
- CAS:Compare and Swap,比较并交换。CAS有3个操作数:内存值V、预期值A、要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。该操作是一个原子操作,被广泛的应用在Java的底层实现中。在Java中,CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现
ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似。
AbstractQueuedSynchronizer 简称AQS
我们来看一下ReentrantLock
构造函数
publicReentrantLock(){
sync=newNonfairSync();}/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/publicReentrantLock(boolean fair){
sync= fair?newFairSync():newNonfairSync();}
构造支持传入fair
来指定是否是公平锁
- FairSync() 公平锁
- NonfairSync() 非公平锁
如果使用无参构造函数,则是非公平锁。
超时机制
在ReetrantLock的tryLock(long timeout, TimeUnit unit)
提供了超时获取锁的功能。它的语义是在指定的时间内如果获取到锁就返回true
,获取不到则返回false
。这种机制避免了线程无限期的等待锁释放。
可重入锁
可重入锁。可重入锁是指同一个线程可以多次获取同一把锁。ReentrantLock和synchronized都是可重入锁。
可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,而ReentrantLock则提供了中断功能。
公平锁与非公平锁。公平锁是指多个线程同时尝试获取同一把锁时,获取锁的顺序按照线程达到的顺序,而非公平锁则允许线程“插队”。synchronized是非公平锁,而ReentrantLock的默认实现是非公平锁,但是也可以设置为公平锁。
读写锁 ReentrantReadWriteLock
现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。
在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock
,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁,描述如下:
线程进入读锁的前提条件:
没有其他线程的写锁,
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。
线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
而读写锁有以下三个重要的特性:
(1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
(2)重进入:读锁和写锁都支持线程重进入。
(3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
源码结构
publicclassReentrantReadWriteLockimplementsReadWriteLock,java.io.Serializable{/** 读锁 */privatefinalReentrantReadWriteLock.ReadLock readerLock;/** 写锁 */privatefinalReentrantReadWriteLock.WriteLock writerLock;finalSync sync;/** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */publicReentrantReadWriteLock(){this(false);}/** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */publicReentrantReadWriteLock(boolean fair){
sync= fair?newFairSync():newNonfairSync();
readerLock=newReadLock(this);
writerLock=newWriteLock(this);}/** 返回用于写入操作的锁 */publicReentrantReadWriteLock.WriteLockwriteLock(){return writerLock;}/** 返回用于读取操作的锁 */publicReentrantReadWriteLock.ReadLockreadLock(){return readerLock;}abstractstaticclassSyncextendsAbstractQueuedSynchronizer{}staticfinalclassNonfairSyncextendsSync{}staticfinalclassFairSyncextendsSync{}publicstaticclassReadLockimplementsLock,java.io.Serializable{}publicstaticclassWriteLockimplementsLock,java.io.Serializable{}}
1、类的继承关系
publicclassReentrantReadWriteLockimplementsReadWriteLock,java.io.Serializable{}
说明:可以看到,ReentrantReadWriteLock实现了ReadWriteLock接口,ReadWriteLock接口定义了获取读锁和写锁的规范,具体需要实现类去实现;同时其还实现了Serializable接口,表示可以进行序列化,在源代码中可以看到ReentrantReadWriteLock实现了自己的序列化逻辑。
2、ReentrantReadWriteLock有五个内部类,五个内部类之间也是相互关联的。内部类的关系如下图所示。
说明:如上图所示,Sync继承自AQS、NonfairSync继承自Sync类、FairSync继承自Sync类(通过构造函数传入的布尔值决定要构造哪一种Sync实例);ReadLock实现了Lock接口、WriteLock也实现了Lock接口。
总结
在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。
在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。
仔细想想,这个设计是合理的:因为当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。
综上:
一个线程要想同时持有写锁和读锁,必须先获取写锁再获取读锁;写锁可以“降级”为读锁;读锁不能“升级”为写锁。
示例
publicclassLockTest{ReadWriteLock lock=newReentrantReadWriteLock();Lock readLock= lock.readLock();Lock writeLock= lock.writeLock();}