ReentrantReadWriteLock实现原理

  在java并发包java.util.concurrent中,除了重入锁ReentrantLock外,读写锁ReentrantReadWriteLock也很常用。在实际开发场景中,在使用共享资源时,可能读操作远远多于写操作。这种情况下,如果对这部分共享资源能够让多个线程读的时候不受阻塞,仅仅在写的时候保证安全性,这样效率会得到显著提升。读写锁ReentrantReadWriteLock便适用于这种场景。

  再描述一下进入读锁和写锁的条件。

  进入读锁: 

      1.没有其他线程的写锁

      2.有写请求且请求线程就是持有锁的线程

  进入写锁:

      1.没有其他线程读锁

      2.没有其他线程写锁

  本篇从源码方面,简要分析ReentrantReadWriteLock的实现原理,以及展示一下它的使用效果。

源码

  这是ReentrantReadWriteLock维护的一对锁

1/** Inner class providing readlock */ 2 private final ReentrantReadWriteLock.ReadLock readerLock; 3 /** Inner class providing writelock */ 4 private final ReentrantReadWriteLock.WriteLock writerLock;

  ReentrantReadWriteLock的构造器中,同时实例化读写锁,同时与ReentrantLock相同,也有公平锁和非公平锁之分

1public ReentrantReadWriteLock(boolean fair) { 2 sync = fair ? new FairSync() : new NonfairSync(); 3 readerLock = new ReadLock(this); 4 writerLock = new WriteLock(this); 5 }

写锁

  获取锁

1public void lock() { 2 sync.acquire(1); 3 } 4//这里与ReentrantLock相同 5public final void acquire(int arg) { 6 if (!tryAcquire(arg) && 7 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 8 selfInterrupt(); 9 } 10 11protected final boolean tryAcquire(int acquires) { 12 Thread current = Thread.currentThread(); 13 int c = getState(); 14 int w = exclusiveCount(c); 15 if (c != 0) { 16 // (Note: if c != 0 and w == 0 then shared count != 0) 17 if (w == 0 || current != getExclusiveOwnerThread()) 18 return false; 19 if (w + exclusiveCount(acquires) > MAX_COUNT) 20 throw new Error("Maximum lock count exceeded"); 21 // Reentrant acquire 22 setState(c + acquires); 23 return true; 24 } 25 if (writerShouldBlock() || 26 !compareAndSetState(c, c + acquires)) 27 return false; 28 setExclusiveOwnerThread(current); 29 return true; 30 }

  这里解析tryAcquire()方法。

  • 获取当前线程
  • 获取状态
  • 获取写线程数
  • 若state不为0,表示锁已被持有。再判断,如果写线程数为0,则读锁被占用,返回false;如果写线程数不为0,且独占线程不是当前线程,表示写锁被其他线程占用没返回false
  • 如果写锁重入数大于最大值MAX_COUNT,抛错
  • 写锁重入,返回true
  • state为0,根据公平锁还是非公平锁判断是否阻塞线程。不需要阻塞就CAS更新state
  • 当前线程设为独占线程,获取写锁,返回true

  释放锁

1public void unlock() { 2 sync.release(1); 3 } 4 5public final boolean release(int arg) { 6 if (tryRelease(arg)) { 7 Node h = head; 8 if (h != null && h.waitStatus != 0) 9 unparkSuccessor(h); 10 return true; 11 } 12 return false; 13 } 14 15protected final boolean tryRelease(int releases) { 16 if (!isHeldExclusively()) 17 throw new IllegalMonitorStateException(); 18 int nextc = getState() - releases; 19 boolean free = exclusiveCount(nextc) == 0; 20 if (free) 21 setExclusiveOwnerThread(null); 22 setState(nextc); 23 return free; 24 }

  分析tryRelease()方法

  • 判断持有写锁的线程是否当前线程,不是则抛错
  • state减1
  • 以新state计算写锁数量,如果为0,表示完全释放;
  • 完全释放就设置独占线程为null
  • 如果独占线程数量不是0,还是更新state,这里就表示多次重入写锁后,释放了一次

读锁

  获取锁

1public void lock() { 2 sync.acquireShared(1); 3 } 4 5public final void acquireShared(int arg) { 6 if (tryAcquireShared(arg) < 0) 7 doAcquireShared(arg); 8 } 9 10protected final int tryAcquireShared(int unused) { 11 Thread current = Thread.currentThread(); 12 int c = getState(); 13 if (exclusiveCount(c) != 0 && 14 getExclusiveOwnerThread() != current) 15 return -1; 16 int r = sharedCount(c); 17 if (!readerShouldBlock() && 18 r < MAX_COUNT && 19 compareAndSetState(c, c + SHARED_UNIT)) { 20 if (r == 0) { 21 firstReader = current; 22 firstReaderHoldCount = 1; 23 } else if (firstReader == current) { 24 firstReaderHoldCount++; 25 } else { 26 HoldCounter rh = cachedHoldCounter; 27 if (rh == null || rh.tid != getThreadId(current)) 28 cachedHoldCounter = rh = readHolds.get(); 29 else if (rh.count == 0) 30 readHolds.set(rh); 31 rh.count++; 32 } 33 return 1; 34 } 35 return fullTryAcquireShared(current); 36 }

  这里分析tryAcquireShared()方法

  • 获取当前线程
  • 获取state
  • 如果写锁数量不为0,且独占线程不是本线程,获得读锁失败。因为写锁被其他线程占用
  • 获取读锁数量
  • 根据公平锁或者非公平锁判断是否应该被阻塞,判断读锁数量是否小于最大值MAX_COUNT,再尝试CAS更新state
  • 以上判断都通过且更新state也成功后,如果读锁为0,记录第一个读线程和此线程占用读锁数量
  • 如果第一个读线程是本线程,表示此时是读锁的重入,则把此线程占用读锁数量+1
  • 如果读锁数量不为0,且此线程也不是第一个读线程,则找到当前线程的计数器,并计数+1
  • 如果在阻塞判断,读锁数量判断和CAS更新是否成功这部分没有通过,则进入fullTryAcquireShared()方法,逻辑与上面的获取类似,以无限循环方式保证操作成功,不赘述。

释放锁

1public void unlock() { 2 sync.releaseShared(1); 3 } 4public final boolean releaseShared(int arg) { 5 if (tryReleaseShared(arg)) { 6 doReleaseShared(); 7 return true; 8 } 9 return false; 10 } 11 12protected final boolean tryReleaseShared(int unused) { 13 Thread current = Thread.currentThread(); 14 if (firstReader == current) { 15 // assert firstReaderHoldCount > 0; 16 if (firstReaderHoldCount == 1) 17 firstReader = null; 18 else 19 firstReaderHoldCount--; 20 } else { 21 HoldCounter rh = cachedHoldCounter; 22 if (rh == null || rh.tid != getThreadId(current)) 23 rh = readHolds.get(); 24 int count = rh.count; 25 if (count <= 1) { 26 readHolds.remove(); 27 if (count <= 0) 28 throw unmatchedUnlockException(); 29 } 30 --rh.count; 31 } 32 for (;;) { 33 int c = getState(); 34 int nextc = c - SHARED_UNIT; 35 if (compareAndSetState(c, nextc)) 36 // Releasing the read lock has no effect on readers, 37 // but it may allow waiting writers to proceed if 38 // both read and write locks are now free. 39 return nextc == 0; 40 } 41 }

  分析tryReleaseShared()方法

  • 获取当前线程
  • 如果当前线程是第一个读线程,则释放firstReader或者第一个读线程的锁计数-1
  • 不是就获得当前线程的计数器。根据计数选择删除此计数器或者减少计数
  • 无限循环更新state  

获取锁和释放锁的源码部分代码就分析放到这里,接下来用代码时间看看ReentrantReadWriteLock的使用效果测试。

1public class ReadWriteLockTest { 2 private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 3 private static ExecutorService executorService = Executors.newCachedThreadPool(); 4 //读操作 5 public static void read(){ 6 try {       //加读锁 7 readWriteLock.readLock().lock(); 8 System.out.println(Thread.currentThread().getName() + " is reading " + System.currentTimeMillis()); 9 Thread.sleep(1000); 10 } catch (InterruptedException e){ 11 12 }finally { 13 readWriteLock.readLock().unlock(); 14 } 15 } 16 //写操作 17 public static void write() { 18 try {       //加写锁 19 readWriteLock.writeLock().lock(); 20 System.out.println(Thread.currentThread().getName() + " is writing "+ System.currentTimeMillis()); 21 Thread.sleep(1000); 22 } catch (InterruptedException e){ 23 24 }finally { 25 readWriteLock.writeLock().unlock(); 26 } 27 } 28 29 30 public static void main(String[] args) { 31 for (int i = 0; i < 3; i++) { 32 executorService.execute(new Runnable() { 33 @Override 34 public void run() { 35 ReadWriteLockTest.read(); 36 } 37 }); 38 39 } 40 for (int i = 0; i < 3; i++) { 41 executorService.execute(new Runnable() { 42 @Override 43 public void run() { 44 ReadWriteLockTest.write(); 45 } 46 }); 47 } 48 } 49}

  执行结果如下:

1pool-1-thread-2 is reading 1549002279198 2pool-1-thread-1 is reading 1549002279198 3pool-1-thread-3 is reading 1549002279198 4pool-1-thread-4 is writing 1549002280208 5pool-1-thread-5 is writing 1549002281214 6pool-1-thread-6 is writing 1549002282224

  可以看到,thread1,2,3在读时,是同时执行。thread4,5,6在写操作是,都差不多间隔1000毫秒。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

JAVA中 ReentrantReadWriteLock读写锁详系教程,包会

一、读写锁简介现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。 针对这种场景,JAVA的并发包提供了读写锁ReentrantReadW

Java中的读写锁ReadWriteLock

ReadWriteLock是JDK中的读写锁接口ReentrantReadWriteLock是ReadWriteLock的一种实现读写锁非常适合读多写少的场景。读写锁与互斥锁的一个重要区别是读写锁允许多个线程同时读共享变量,这是读写锁在读多写少的情况下性能较高的原因。读写锁的原则:多个线程可同时读共享变量只允许一