什么是读写锁?
在没有写操作的时候,多个线程同时读一个资源没有任何问题,允许多个线程同时读取共享资源(读读可以并发)。
如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写操作了(读写,写读,写写互斥)。
在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。
读写锁在内部维护了一对相关的锁,一个用于只读操作,称为读锁;一个用于写入操作,称为写锁。
线程进入读锁的前提条件:没有其他线程的写锁;没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。
线程进入写锁的前提条件:没有其他线程的读锁;没有其他线程的写锁。
读写锁的重要特性
公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
可重入:读锁和写锁都支持线程重入。以读写线程为例:读线程获取读锁后,能够再次获取读锁。写线程在获取写锁之后能够再次获取写锁,同时也可以获取读锁。
锁降级:遵循获取写锁、再获取读锁最后释放写锁的次序,写锁能够降级成为读锁。
ReentrantReadWriteLock的使用
ReentrantReadWriteLock是可重入的读写锁实现类。
在它内部,维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。
只要没有Writer线程,读锁可以由多个Reader线程同时持有。
也就是说,写锁是独占的,读锁是共享的。
ReentrantReadWriteLock使用的注意事项
读锁不支持条件变量
重入时升级不支持:持有读锁的情况下去获取写锁,会导致获取永久等待
重入时支持降级:持有写锁的情况下可以去获取读锁
ReentrantReadWriteLock的应用场景
有共享变量并且读多写少的场景!
锁降级
锁降级指的是写锁降级成为读锁。主要是为了防止数据没有刷回到主内存,导致其他线程取到的值不一致!
没有锁升级:因为大量线程获取读锁,其中一个线程变为写锁改了数据,其他线程不可知,导致其他线程取到的值不一致!
如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。
锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
锁降级可以帮助我们拿到当前线程修改后的结果而不被其他线程所破坏,防止更新丢失。
ReentrantReadWriteLock锁的使用方式
importjava.util.HashMap;importjava.util.Map;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantReadWriteLock;//处理读读操作用同事进行;读写,写读,写写都会同时进行!publicclassReentrantReadWriteLockTest1{staticMapString,Objectmap=newHashMapString,Object();staticReentrantReadWriteLockrwl=newReentrantReadWriteLock();staticLockr=rwl.readLock();staticLockw=rwl.writeLock();//读操作,用读锁publicfinalObjectread(Stringkey)throwsInterruptedException{r.lock();try{System.out.println(System.currentTimeMillis()+"读锁获取成功...");Thread.sleep();System.out.println(System.currentTimeMillis()+"读锁执行完成...");returnmap.get(key);}finally{System.out.println(System.currentTimeMillis()+"读锁释放...");r.unlock();}}//写操作,用写锁publicfinalObjectwrite(Stringkey,Objectvalue)throwsInterruptedException{w.lock();try{System.out.println(System.currentTimeMillis()+"写锁获取成功...");Thread.sleep();System.out.println(System.currentTimeMillis()+"写锁执行完成...");returnmap.put(key,value);}finally{System.out.println(System.currentTimeMillis()+"写锁释放...");w.unlock();}}publicstaticvoidmain(String[]args){ReentrantReadWriteLockTest1lock=newReentrantReadWriteLockTest1();newThread(()-{try{lock.read("1");//lock.write("1","2");}catch(InterruptedExceptione){e.printStackTrace();}}).start();newThread(()-{try{lock.read("1");//lock.write("1","2");}catch(InterruptedExceptione){e.printStackTrace();}}).start();}}
锁降级的使用方式
importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantReadWriteLock;publicclassReentrantReadWriteLockTest1{privatefinalReentrantReadWriteLockrwl=newReentrantReadWriteLock();privatefinalLockreadLock=rwl.readLock();privatefinalLockwriteLock=rwl.writeLock();privatevolatilebooleanupdate=false;publicvoidtest(){readLock.lock();if(!update){//必须先释放读锁readLock.unlock();//锁降级从写锁获取到开始writeLock.lock();try{if(!update){//TODO准备数据的流程(略)update=true;}//=====这行代码就是锁降级的开始代码=====readLock.lock();}finally{writeLock.unlock();}//锁降级完成,写锁降级为读锁}try{//TODO使用数据的流程(略)}finally{//=====这行代码就是锁降级的结束代码=====readLock.unlock();}}}
熟悉下面的这些技术,学习效果更佳
「并发编程」回环栅栏CyclicBarrier从入门到源码精通
「并发编程」闭锁CountDownLatch从入门到源码精通
「并发编程」信号量Semaphore从入门到源码精通
「并发编程」通俗易懂的来学ReentrantLock锁源码「并发编程」并发包中工具类的基础:AQS
ReentrantReadWriteLock源码流程图
ReentrantReadWriteLock源码流程图.png
ReentrantReadWriteLock读写状态源码分析:一个变量维护多个状态!
//共享的移位量staticfinalintSHARED_SHIFT=16;//共享的单位:00010000staticfinalintSHARED_UNIT=(1SHARED_SHIFT);//共享的最大数量:staticfinalintMAX_COUNT=(1SHARED_SHIFT)-1;//独占的单位:staticfinalintEXCLUSIVE_MASK=(1SHARED_SHIFT)-1;/**Returnsthenumberofsharedholdsrepresentedincount*///计算共享的数量(读锁):高16位表示。读锁可以同时被多个线程持有,每个线程持有的读锁支持重入的特性,所以需要对每个线程持有的读锁的数量单独计数,这就需要用到HoldCounter计数器staticintsharedCount(intc){returncSHARED_SHIFT;}/**Returnsthenumberofexclusiveholdsrepresentedincount*///计算独占的重入数量(写锁):低16位表示。staticintexclusiveCount(intc){returncEXCLUSIVE_MASK;}
ReentrantReadWriteLock的读锁计数器HoldCounter源码分析
/***读锁的本质是共享锁,一次共享锁的操作就相当于对HoldCounter计数器的操作。*获取共享锁,则该计数器+1,释放共享锁,该计数器-1。只有当线程获取共享锁后才能对共享锁进行释放、重入操作。*HoldCounter是用来记录读锁重入数的对象*/staticfinalclassHoldCounter{intcount=0;//Useid,notreference,toavoidgarbageretentionfinallongtid=getThreadId(Thread.currentThread());}/***通过ThreadLocalHoldCounter类,HoldCounter与线程进行绑定。*HoldCounter是绑定线程的一个计数器,而ThreadLocalHoldCounter则是线程绑定的ThreadLocal。*ThreadLocalHoldCounter是ThreadLocal变量,用来存放不是第一个获取读锁的线程的其他线程的读锁重入数对象*/staticfinalclassThreadLocalHoldCounterextendsThreadLocalHoldCounter{publicHoldCounterinitialValue(){returnnewHoldCounter();}}
ReentrantReadWriteLock构造方法源码分析
/***无参构造直接调用有参构造,非公平模式!*/publicReentrantReadWriteLock(){this(false);}/***传入是否公平模式*/publicReentrantReadWriteLock(booleanfair){//区别为公平模式sync=fair?newFairSync():newNonfairSync();//初始化读锁readerLock=newReadLock(this);//初始化写锁writerLock=newWriteLock(this);}
ReentrantReadWriteLock写锁加锁源码分析
写锁加锁获取锁逻辑图.png
/***直接调用AQS的获取独占锁逻辑*/publicvoidlock(){sync.acquire(1);}/***AQS的获取独占锁逻辑*/publicfinalvoidacquire(intarg){//tryAcquire源码在下方、acquireQueued在AQS中,与ReentrantLock实现方式一致。if(!tryAcquire(arg)acquireQueued(addWaiter(Node.EXCLUSIVE),arg))selfInterrupt();}/***尝试获取锁*/protectedfinalbooleantryAcquire(intacquires){//获取当前线程Threadcurrent=Thread.currentThread();//获取当前的状态intc=getState();//获取写锁的状态intw=exclusiveCount(c);//总状态不为0,可能有读有些if(c!=0){//(Note:ifc!=0andw==0thensharedcount!=0)//写锁为0,或者当前线程不是持有锁的线程。返回尝试获取锁失败if(w==0
current!=getExclusiveOwnerThread())returnfalse;//写锁数量太多,抛异常!if(w+exclusiveCount(acquires)MAX_COUNT)thrownewError("Maximumlockcountexceeded");//Reentrantacquire//执行到这里,是当前的线程,进行重入处理setState(c+acquires);//返回尝试获取锁成功returntrue;}//同步队列中有排队的,并且不可以重入的时候,返回尝试获取锁失败//cas变更实际的状态失败,返回尝试获取锁失败if(writerShouldBlock()
!