「java.util.concurrent并发包」之 ReentrantReadWriteLock

Posted 宇宙唯心

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「java.util.concurrent并发包」之 ReentrantReadWriteLock相关的知识,希望对你有一定的参考价值。

一 引言

在多线程的环境下,对同一份数据进行读写,会涉及到线程安全的问题。比如在一个线程读取数据的时候,另外一个线程在写数据,而导致前后数据的不一致性;一个线程在写数据的时候,另一个线程也在写,同样也会导致线程前后看到的数据的不一致性。这时候可以在读写方法中加入互斥锁,任何时候只能允许一个线程的一个读或写操作,而不允许其他线程的读或写操作,这样是可以解决这样以上的问题,但是效率却大打折扣了。因为在真实的业务场景中,一份数据,读取数据的操作次数通常高于写入数据的操作,而线程与线程间的读读操作是不涉及到线程安全的问题,没有必要加入互斥锁,只要在读-写,写-写期间上锁就行了

读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的。

ReentrantReadWriteLock是可重入的读写锁,允许多个读线程获得ReadLock,但只允许一个写线程获得WriteLock

二 准备

读写锁的机制:

   "读-读" 不互斥
   "读-写" 互斥
   "写-写" 互斥
 
ReentrantReadWriteLock不支持锁升级(从读锁变成写锁)
技术分享
ReadWriteLock rtLock = new ReentrantReadWriteLock();
 rtLock.readLock().lock();
 System.out.println("get readLock.");
 rtLock.writeLock().lock();
 System.out.println("blocking");
锁升级

这个代码会死锁,没释放读锁就去申请写锁

ReentrantReadWriteLock支持锁降级(从写锁变成读锁)
技术分享
ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.writeLock().lock();
System.out.println("writeLock");

rtLock.readLock().lock();
System.out.println("get read lock");
锁降级

  以上这段代码虽然不会导致死锁,但没有正确的释放锁。从写锁降级成读锁,并不会自动释放当前线程获取的写锁,仍然需要显示的释放,否则别的线程永远也获取不到写锁。

 

三 javadoc的例子

技术分享
 1 class CachedData {
 2   Object data;
 3   volatile boolean cacheValid;
 4   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 5 
 6   public void processCachedData() {
 7     rwl.readLock().lock();
 8     if (!cacheValid) {
 9       // Must release read lock before acquiring write lock
10       rwl.readLock().unlock();
11       rwl.writeLock().lock();
12       try {
13         // Recheck state because another thread might have,acquired write lock and changed state before we did.
14         if (!cacheValid) {
15           data = ...
16           cacheValid = true;
17         }
18         // 在释放写锁之前通过获取读锁降级写锁(注意此时还没有释放写锁)
19         rwl.readLock().lock();
20       } finally {
21         rwl.writeLock().unlock(); // 释放写锁而此时已经持有读锁
22       }
23     }
24 
25     try {
26       use(data);
27     } finally {
28       rwl.readLock().unlock();
29     }
30   }
31 }
ReadWriteLock实例

注意最后的释放写锁「line21」,在之前是要加读锁「line19」的,因为在get过程中,可能有其他线程竞争到锁或是更新数据,会产生脏读。

 

四 缓存例子
技术分享
 private static Map<Integer, Integer> cache = Maps.newHashMap();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public Integer get(Integer key) {
        Integer value;
        readWriteLock.readLock().lock();
        try {
            value = cache.get(key);
            if (value == null) {
                readWriteLock.readLock().unlock();
                readWriteLock.writeLock().lock();
                try {
                    if (value == null) {
                        value = 1; // 从数据库读取等
                    }
                    readWriteLock.readLock().lock();
                } finally {
                    readWriteLock.writeLock().unlock();
                }
            }
        } finally {
            readWriteLock.readLock().unlock();
        }
        return value;
    }

    public void put(Integer key, Integer value) {
        readWriteLock.writeLock().lock();
        cache.put(key, value);
        readWriteLock.writeLock().unlock();
    }
缓存
 
 

以上是关于「java.util.concurrent并发包」之 ReentrantReadWriteLock的主要内容,如果未能解决你的问题,请参考以下文章

java并发包java.util.concurrent详解

举例详解 java.util.concurrent 并发包 4 种常见类

举例详解 java.util.concurrent 并发包 4 种常见类

java多线程---------java.util.concurrent并发包----------ThreadPoolExecutor

并发包java.util.concurrent.CountDownLatch

Java学习笔记—多线程(java.util.concurrent并发包概括,转载)