当前位置: 首页 > 后端技术 > Java

【JAVA并发编程】读写锁ReentrantReadWriteLock实现分析

时间:2023-04-01 16:09:59 Java

一、介绍读写锁允许多个读线程同时访问,但是当写线程访问时,所有读线程和其他写线程都被阻塞。一个读写锁维护两个锁,一个读锁和一个写锁。获取读写锁分为以下两种情况:同一个线程:线程获取读锁后,可以再次获取读锁,但无法获取写锁。线程获得写锁后,可以再次获得写锁,也可以再次获得读锁。不同线程:线程A获取读锁后,线程B可以再次获取读锁,但是不能获取写锁。A线程获得写锁后,B线程就无法获得读锁和写锁。2.读写锁实例publicclassCache{staticMapmap=newHashMap();静态ReentrantReadWriteLockrwl=newReentrantReadWriteLock();静态锁r=rwl.readLock();静态锁w=rwl.writeLock();publicstaticfinalObjectget(Stringkey){r.lock();尝试{返回map.get(key);}最后{r.unlock();}}publicstaticfinalObjectput(Stringkey,Objectvalue){w.lock();尝试{returnmap.put(key,value);}最后{w.unlock();}}}在上面的例子中,Cache结合了一个非线程安全的HashMap作为缓存实现,通过读写锁来保证Cache是??线程安全的。3.读写锁的实现分析3.1读写状态的设计回顾AQS的实现,同步状态表示获取锁的次数,读写锁的自定义同步器需要维护多个同步状态(整数变量)。一个reader线程和一个writer线程的状态,这个状态的设计成为读写锁设计的关键。如果在一个整型变量上维护多个状态,则需要对该变量进行“按位切分使用”。读写锁将变量分为两部分,高16位表示读,低16位表示写。划分方法如下。上图中的同步状态表示一个线程获得了写锁,两次重新进入,连续两次获得读锁。读写锁时如何判断读写状态?答案是通过位运算。假设当前同步状态为S,写状态等于S&0x0000FFFF(擦除高16位),读状态等于S>>>16(无符号补0右移16位)。写状态加1时等于S+1,读状态加1时等于S+(1<<16),即S+0x00010000。3.2写锁的获取与释放写锁是一种支持可重入的排它锁。如果当前线程已经有写锁,增加写状态。如果当前线程获取写锁,读锁已经获取(读状态不为0)或者线程不是获取写锁的线程,则当前线程进入等待状态。代码如下protectedfinalbooleantryAcquire(intacquires){Threadcurrent=Thread.currentThread();intc=getState();intw=exclusiveCount(c);if(c!=0){//存在读锁或者当前获取线程不是已经获取写锁的线程if(w==0||current!=getExclusiveOwnerThread()){returnfalse;}if(w+exclusiveCount(acquires)>MAX_COUNT)thrownewError("超过最大锁计数");setState(c+获取);返回真;}if(writerShouldBlock()||!compareAndSetState(c,c+acquires)){returnfalse;}setExclusiveOwnerThread(current);returntrue;}如果有读锁(即使Onlyifthecurrentthreadacquiresthereadlockdoesnotwork),写锁也获取不到。原因是读写锁必须保证写锁的操作对读锁是可见的,所以只有等待其他读线程释放读锁才能获得写锁。由当前线程获取,一旦获取到写锁,后续其他读写线程的访问就会被阻塞。3.3读锁的获取和释放读锁是一种支持重入的共享锁。可以同时被多个线程获取。当写状态为0时,读锁总是会成功获取,所做的只是增加读状态。如果当前线程获得了读锁,而写锁已经被其他线程获得,则进入等待状态。protectedfinalinttryAcquireShared(intunused){for(;;){intc=getState();intnextc=c+(1<<16);if(nextc