首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java Concurrent ReentrantReadWriteLock(Java 10)

前言

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

代码语言:javascript
复制
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

代码语言:javascript
复制
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位为写锁锁定,以这个为基础维护读锁及写锁之间的关系: 并且内部维持这些静态属性:

代码语言:javascript
复制
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)

代码语言:javascript
复制
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 原生方法:

代码语言:javascript
复制
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)

代码语言:javascript
复制
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,表示释放的读锁是写锁降级来的。

代码语言:javascript
复制
public void unlock() {
    sync.releaseShared(1);
}

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

下一篇
举报
领券