JAVA多线程之——读写锁 ReentrantReadWriteLock

ReentrantReadWriteLock

锁的类型有很多,前面学习了阻塞锁、互斥锁、自旋锁等。今天学习读写锁。所谓读写锁就是维护了一个读锁和写锁。但是读锁和写锁互斥、写锁和写锁互斥。读锁和读锁不互斥。既允许多个读锁同时读。但是同时间只有一个写锁写。读写锁也是可重入锁。
根据JDK文档描述,所以读写锁以下几个特点:
1. 读写锁对于读锁与写锁的获取顺序不会干涉。
2. 非公平模式下可能会导致饥饿状态。也就是说可能会无限推迟一个读锁或者写锁线程获取锁。
3. 公平模式下当锁被释放时候,会为等待最长时间的写锁分配锁,如果有线程的等待时间大于所有写线程的等待时间,那就分配这些线程读锁。
4. 读写锁是可以重入的。但是要切记其中的锁互斥问题。也就是读锁要重入,就必须所有的写锁都已经释放。写锁可以重入读锁,但是读锁不能重入写锁。
5. 重入还允许从写入锁降级为读取锁,实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。
6. 写入锁提供了一个 Condition 实现,对于写入锁来说,该实现的行为与 ReentrantLock.newCondition() 提供的Condition 实现对 ReentrantLock 所做的行为相同。当然,此 Condition 只能用于写入锁。
读取锁不支持 Condition,readLock().newCondition() 会抛出 UnsupportedOperationException。
7. 此类支持一些确定是读取锁还是写入锁的方法。这些方法设计用于监视系统状态,而不是同步控制。
先来看一下部分源码:

/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;

代码中可以看到,读写锁中包含了一个读锁和一个写锁。

public static class WriteLock implements Lock, java.io.Serializable {
   private static final long serialVersionUID = -4992448646407690164L;
   private final Sync sync;

   /**
    * Constructor for use by subclasses
    *
    * @param lock the outer lock object
    * @throws NullPointerException if the lock is null
    */
   protected WriteLock(ReentrantReadWriteLock lock) {
       sync = lock.sync;
   }
}       

这是写锁的一部分源码,可以看出写锁实现的就是Lock。通过前面学习了解了,Lock的一个实现原理是基于一个内部类Sync 实现AQS的方式。而AQS底层维护的是一个修改过的CLH队列。
AQS如何用一个状态同时表示读锁和写锁?

/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

/** Returns the number of shared holds represented in count  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

看注释可以知道,一个整型32位,然后将它分为两部分,高16位表示读锁的线程数量,低16位表示写锁的线程数量。
如何统计每个线程的重入数?

    abstract static class Sync extends AbstractQueuedSynchronizer {
     /**
      * 每个线程特定的 read 持有计数。存放在ThreadLocal,不需要是线程安全的。
      */
     static final class HoldCounter {
         int count = 0;
         // 使用id而不是引用是为了避免保留垃圾。
         final long tid = Thread.currentThread().getId();
     }
     /**
      * 如果ThreadLocal没有当前线程的计数,则new一个,再放进ThreadLocal里。
      * 可以直接调用 get。
      * */
     static final class ThreadLocalHoldCounter
         extends ThreadLocal {
         public HoldCounter initialValue() {
             return new HoldCounter();
         }
     } 
     /**
     * 保存当前线程重入读锁的次数的容器。在读锁重入次数为 0 时移除。
     */
    private transient ThreadLocalHoldCounter readHolds;
    }

分析源码可以看出,每个读线程都维护自己一个重入数的一个ThreadLocal变量readHolds。这样就能各自统计自己的重入次数。

写一段小代码来实战一下读写锁的用法。JDK文档中也有一个经典的例子。

public class ReadWriteLockTest {
        private static Map  map = new HashMap();
        private static Object obj  = null;
        private static final ReentrantReadWriteLock  lock = new ReentrantReadWriteLock();
        private static  Lock  readLock = lock.readLock();
        private static  Lock  writeLock = lock.writeLock();
        public static Object get(String key) {
            try{
                readLock.lock();
                obj = map.get(key);
                if(null == obj) {
                    try{
                        readLock.unlock();
                        writeLock.lock();
                        if(null == obj) {
                            obj = "abc";
                            map.put(key,obj);
                        }

                    }finally{
                        readLock.lock();
                        writeLock.unlock();
                    }
                }

            }finally{
                readLock.unlock();


            }
            if( null == obj) {
                readLock.unlock();
                writeLock.lock();
                obj = map.get(key);
            }
            return obj;
        }

}

基本就是实现一个缓存的机制。多个线程可以去读取。每次只有一个线程可以写入。

你可能感兴趣的:(java,并发,java,多线程)