ReentrantReadWriteLock源码分析

一、简介

       如果在系统中存在多个线程对对象或者缓存进行多或写的操作,但是读操作要远远的多于写操作的。那么就要保证写操作的结果对所有读操作是可见的。

     由此可见在多个线程执行写操作时,多线程不能同时执行,必须获取写的锁才能只能写操作。要保证写操作的结果对所有读操作是可见的,就必须在读操作的时候写操作不能获取锁。在写操作时候也不能进行读操作。在多个线程执行读操作,多线程可以并发执行。因此要保证写操作时,线程是独占的状态。而在读操作的时候是共享同步状态。

二、实例

ReentrantReadWriteLock示例:

/**
 * @Author: wangxh
 */
public class TestLock {

    private Map<String,Object> param = new HashMap<String,Object>();

    private ReadWriteLock lock = new ReentrantReadWriteLock();

    private Lock readLock = lock.readLock();
    private Lock writeLock = lock.writeLock();

    private boolean isUpdate;

    public void get(String key){
        readLock.lock();
        try{
            System.out.println("读操作开始");
            System.out.println(param.get(key));
            System.out.println("读操作结束");
        }finally {
            readLock.unlock();
        }
    }

    public void put(String key,String value){
        writeLock.lock();
        try{
            System.out.println("写操作开始");
            param.put(key, value);
            System.out.println("写操作结束");
        }finally {
            writeLock.unlock();
        }
    }
}

     通过实现readLock和writeLock的锁来保证get方法和put方法达到读写的多线程安全。

三、源码分析

     1.整体结构分析

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** 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 ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

    abstract static class Sync extends AbstractQueuedSynchronizer

    static final class NonfairSync extends Sync

    static final class FairSync extends Sync

    public static class ReadLock implements Lock, java.io.Serializable

    public static class WriteLock implements Lock, java.io.Serializable

}

     ReentrantReadWriterLock在公平锁和非公平锁的实现上。通过实现一个内部类Sync(继承AQS)。NonfairSync和FairSync都是通过继承Sync来实现 wiriterShouldBlock和readerShouldBlock。

    上文提到过的读锁和写锁就是通过内部类:ReadLock和WriterLock实现的。同时在构造函数中可以指定锁的类型。即ReadLock和WriterLock都是调用ReentrantReadWriterLock的sync对象因此保证了锁的一致性。

                    

ReadLockWriteLock描述
lock()sync.acquireShared(1)sync.acquire(1)由此可见ReadLock调用的是AQS的共享锁,WriterLock是独占所。
lockInterrupibly()sync.acquireSharedInterruptibly(1)sync.acquireInterruptibly(1)
tryLock()sync.tryReadLock(1)sync.tryWriteLock(1)
unlock()sync.releaseShared(1)sync.release(1)

二、Sync类的分析

  1.锁的标记和保存

        //AQS的state字段是32位的int类型,因此高16位保存写锁读锁的状态,低16位保存写锁的状态
        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; }

      获取写锁的重入数量 exclusiveCount方法。获取读锁的重入数量就是sharedCount方法。这里其实用到了位分割的思想。

 

 2.写锁的获取和释放

protected final boolean tryAcquire(int acquires) {
          
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c); //获取独占锁的重入次数
            if (c != 0) {  //state不为0表示读锁此时被占用
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread()) //判断如果重入次数为0,或者不是持有锁的线程返回false
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT) //锁重入最大值判断
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires); //更新重入次数
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires)) //state为0,直接尝试cas
                return false;
            setExclusiveOwnerThread(current); //设置持有锁的线程
            return true;
        }

tryRelease

protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively()) //判断持有线程和当前线程是否相同
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases; //减去持有次数
            boolean free = exclusiveCount(nextc) == 0; //判断是否持有锁的线程已经释放完毕
            if (free)
                setExclusiveOwnerThread(null);  //判断释放独占锁
            setState(nextc); //更新重入次数
            return free;
        }

 

3.读锁的获取和释放实现

  protected final int tryAcquireShared(int unused) {
           
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&  
                getExclusiveOwnerThread() != current) //处于独占模式或者不是当前线程持有锁,获取失败
                return -1;
            int r = sharedCount(c); //获取共享锁重入次数
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) { 
                    //判断策略是否要求阻塞以及最大重入次数、CAS更新
                //如果是第一次获得锁或者是相同线程获取锁,就更新firstReader ,firstReaderHoldCount 。当有其他线程获得共享锁那么获取ThreadLoacl的变量副本readHolds更新自己线程的HoldCounter 。
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

 

 

   protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            //这个也是判断各自的变量副本去更新对应的线程的重入次数
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            //获取共享锁的重入次数,CAS更新锁的次数。道理都是差不多的
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

 

四、总结

  通过源码可以看得出来再读锁获取的时候写锁是不能被持有的,但是在获取写锁的时候可以去获取读锁。

 因此可以进行对锁的降级,即:在获取的写锁的时候可以获取读锁。

在大部分的场景下,读的操作并发要高于写的操作。所以读写锁的性能要高效的。而且读写锁也是基于AQS上构建的实现。有时间操作一下AQS.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值