美文网首页Java 并发编程&JUC源码解析
Java Concurrent ReentrantReadWri

Java Concurrent ReentrantReadWri

作者: 邹志全 | 来源:发表于2019-07-17 21:50 被阅读0次

    前言

    首先明确一点,ReentrantReadWriteLock 指的并不是一个锁,而是管理着读锁&写锁这样的一组锁,读锁是可共享的(shared),写锁是独占的。这么设计是有原因的,读操作之前互不影响,而写操作需要确保对于读操作是完整且结果可见的。其中ReentrantReadWriteLock实现上跟ReentrantLock类似,可选择公平&非公平,可重入等,里面持有的readlock和writelock都持有继承自AQS的Sync,依赖于AQS实现对应的功能。

    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;
    

    使用

    使用上,我们可以定义一个ReentrantLock来获取对应的ReadLock及WriteLock完成对应的功能,下面是一个简单的demo,可以执行一下,看一下具体的效果。

    demo

    public class ReadWriteLockDemo {
        static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        public static void main(String[] args) {
            Data data = new Data();
            Worker t1 = new Worker(data, false);
            //写
            Worker t2 = new Worker(data, true);
            //读
            Worker t3 = new Worker(data, true);
            //读
            t1.start();
            t2.start();
            t3.start();
        }
        static class Worker extends Thread {
            Data data;
            Boolean read;
            public Worker(Data data, Boolean read) {
                this.data = data;
                this.read = read;
            }
            public void run() {
                if (read)
                    data.read(); else
                    data.write();
            }
        }
        static class Data {
            ReadWriteLock lock = new ReentrantReadWriteLock();
            Lock read = lock.readLock();
            Lock write = lock.writeLock();
            public void write() {
                try {
                    Thread.sleep(2000);
                    //
                }
                catch (Exception e) {
                }
                write.lock();
                System.out.println(Thread.currentThread() + " write:begin "
                        + sdf.format(new Date()));
                try {
                    Thread.sleep(5000);
                    //
                }
                catch (Exception e) {
                }
                finally {
                    System.out.println(Thread.currentThread() + " write:end "
                            + sdf.format(new Date()));
                    write.unlock();
                }
            }
            public int read() {
                read.lock();
                System.out.println(Thread.currentThread()+ " read :begin "
                        + sdf.format(new Date()));
                try {
                    Thread.sleep(5000);
                    //
                }
                catch (Exception e) {
                }
                finally {
                    System.out.println(Thread.currentThread() + " read :end "
                            + sdf.format(new Date()));
                    read.unlock();
                }
                return 1;
            }
        }
    }
    

    说原理之前先说结论:

    取锁:

    1、如果当前没有写锁或读锁时,第一个获取锁的线程都会成功。
    2、如果当前已经有了读锁,那么这时获取写锁将失败,获取读锁有可能成功也有可能失败。
    3、如果当前已经有了写锁,那么这时获取读锁或写锁,如果线程相同(可重入),那么成功

    释放:

    1、如果当前是写锁被占有了,只有当写锁的标示数据降为0时才认为释放成功,否则失败。因为只要有写锁,那么除了占有写锁的那个线程,其他线程即不可以获得锁。
    2、如果当前是读锁被占有了,那么只有在写锁的个数为0时才认为释放成功。

    原理

    与Reentrant相同的实现部分不做介绍,主要说不同之处:
    读写锁里面存在这样几个静态内部类:
    Sync entends AQS(内含:ThreadLocalHoldCounter extends ThreadLocal、HoldCounter)、FairLock extends SyncNonFairLock extends SyncReadLockWriteLoc、(其中ReadLock、WriteLock 分别持有一个Sync)在Sync中维护了Read和Write的使用之间的关系,并且Read、Write全部依赖于Sync中的逻辑,内部类中并没有具体的逻辑实现,所以说看整个ReentrantReadWriteLock 看Sync的核心实现就OK了。

    Sync

    内部state分为两个无符号的short,读锁锁定的高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;
    
    WriteLock

    state变量的低16位,当state低16位为0,表示当前写锁没有被占有,反之表示写锁被某个写线程占有。
    占用条件&过程
    1、读锁未被占用(state高16位为0) ,写锁未被占用(state低16位为0)或 占用写锁的线程是当前线程
    2、writerShouldBlock()方法返回false,即不阻塞写线程
    3、写锁占有量小于最大值(2^16 -1),否则抛出Error("Maximum lock count exceeded")
    4、通过CAS竞争将写锁状态+1(将state低16位同步+1)

    protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        int c = getState();
        int w = exclusiveCount(c);
        if (c != 0) {
            // (Note: if c != 0 and w == 0 then shared count != 0)
            if (w == 0 || current != getExclusiveOwnerThread())
                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))
            return false;
        setExclusiveOwnerThread(current);
        return true;
    }
    

    释放过程:
    写锁的释放过程是state低16位同步递减为0的过程,当state的高16位都为0表示写锁释放完毕,唤醒head节点下一个SIGNAL状态的节点的线程。如果该写锁占有线程未释放写锁前还占用了读锁,那么写锁释放后该线程就完全转换成了读锁的持有线程。
    调用的AQS 原生方法:

    public void unlock() {
        sync.release(1);
    }
    
    ReadLock

    当state的高16位等于0,表示当前读锁未被占有;当state的高16位大于0,表示当前读锁可能被一个或多个线程占有,多于一个占有读锁的线程,允许重入。
    占用条件&过程:
    1、当前的写锁未被占有(state低16位为0) 或者当前线程是写锁占有的线程
    2、readerShouldBlock()方法返回false
    3、当前读锁占有量小于最大值(2^16 -1)
    4、成功通过CAS操作将读锁占有量+1(AQS的state高16位同步加1)

    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)) {
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null ||
                    rh.tid != LockSupport.getThreadId(current))
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            return 1;
        }
        return fullTryAcquireShared(current);
    }
    

    如果逻辑结果小于0,表示当前无条件获得,则将该线程加入阻塞队列(doAcquireShared)
    释放过程:
    读锁的释放过程即AQS的state高16位同步递减为0的过程,当state的高16位都为0表示读锁释放完毕,如果此时写锁状态为0(即该读锁不是写锁降级来的),唤醒head节点下一个SIGNAL状态的节点的线程。如果读锁的占有数不为0,表示读锁未完全释放。或者写锁的占有数不为0,表示释放的读锁是写锁降级来的。

    public void unlock() {
        sync.releaseShared(1);
    }
    

    看完ReentrantLock&ReentrantReadWriteLock,是不是发现满满的都是CAS和for ;;,这就是非阻塞的一种实现原理,有利有弊吧。

    相关文章

      网友评论

        本文标题:Java Concurrent ReentrantReadWri

        本文链接:https://www.haomeiwen.com/subject/nsiclctx.html