ReentrantReadWriteLock

关于读写锁状态的存取

// ReentrantReadWriteLock.Sync
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; }// 高16位存储读锁
/** Returns the number of exclusive holds represented in count  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }// 低16位存储写锁
// 设置读锁的状态
compareAndSetState(c, c + SHARED_UNIT)
// 设置写锁的状态
compareAndSetState(c, c + 1)

使用state(int类型,32位)的高16位存储读锁状态,低16位存储写锁状态

读写锁的结构

ReentrantReadWriteLock本身不是一把锁,但是它管理着两把锁,读锁和写锁。写锁是独占锁,读锁是共享锁。

这里ReadWriteLock只是一个接口,并不是一把锁。

private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;

公平锁和非公平锁

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock() {
        return false; // writers can always barge
    }
    final boolean readerShouldBlock() {// 返回false会尝试去获取锁
        return apparentlyFirstQueuedIsExclusive();
    }
}
// 这个方法指用在获取读锁上,即获取共享锁上
// 即下一个要被唤醒的节点是不是独占节点,即这个节点是不是在申请写锁
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    // 下一个要唤醒的节点不是共享节点,就返回true
    // 即如果下一个要唤醒的是申请写锁的线程,就阻塞读锁的获取
    return (h = head) != null &&
        (s = h.next)  != null &&
        !s.isShared()         &&
        s.thread != null;
}

static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    // h!=t证明起码有一个线程在等待 s.thread!= currentThread 因为读,写锁都是可重入的,所以,如果等待的这个线程正好是当前线程
    // 可以直接放行让它获取锁
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

在ReentrantLock中,公平锁与非公平锁的区别是,非公平锁不管同步队列中有没有线程在排队,都会先去获取锁。而公平锁,就是直接先去排队。

那么同样的定义,在ReentrantReadWriteLock中也体现得很明显。

FairSync和NonFairSync都重写了writerShouldBlock,readerShouldBlock方法。

writerShouldBlock用在获取独占锁,也就是写锁的方法上。

// Sync  acquire方法会调用tryAcquire方法获取锁,acquire方法会被WriteLock.lock方法调用,即获取写锁的时候调用
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())// c != 0,w == 0,证明有线程获取了读锁
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    // 如果是公平锁,writerShouldBlock将会看前面有没有线程在排队,有排队的,writerShouldBlock才返回true,即这里直接返回false(获取锁失败)
    // 如果是非公平锁,这里返回false,会调用CAS设置state,设置失败才返回false
    // 所以,就一个writerShouldBlock方法就可以实现了公平锁和非公平锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

获取写锁流程图如下:

readerShouldBlock方法被tryAcquireShared和fullyTryAcquireShared调用

// Sync tryAcquireShared会被acquireShared调用,acquireShared被ReadLock.lock调用,即获取读锁的时候调用
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 != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

 读锁和写锁

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

    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        sync.acquireShared(1);
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean tryLock() {
        return sync.tryReadLock();// 这是唯一一个没有用AQS方法的方法
    }
    
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

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

    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }
}
// ReentrantReadWriteLock.Sync
final boolean tryReadLock() {
    Thread current = Thread.currentThread();
    for (;;) {
        int c = getState();
        // exclusiveCount指的是获取写锁的数目
        // 如果有别的线程已经获取了写锁,则失败(如果是同一个线程获取了写锁,则无所谓,因为同一个线程的读写肯定不会冲突)
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return false;
        int r = sharedCount(c);// 读锁的数目
        if (r == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {// 获取读锁成功
            if (r == 0) {// 如果原来没有线程获取到读锁
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                // 一个同步器有一个cachedHoldCounter,一个Lock有一个同步器
                // cachedHoldCounter是上一个获取到读锁的线程的获取锁的数目
                // 如果当前线程获取了读锁,更新占有锁的数目
                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 true;
        }
    }
}

可以看到ReadLock中,除了tryLock方法,其他调的都是AQS提供的方法。其中lock方法也在公平锁和非公平锁中讲过了。

tryLock的流程也跟lock的流程基本一致,除了lock中加了readerShouldBlock方法的判断。这里就不详解了。

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

    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    public void lock() {
        sync.acquire(1);
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock( ) {
        return sync.tryWriteLock();// 也是只有这个方法是自定义的
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

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

    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
    
    public int getHoldCount() {
        return sync.getWriteHoldCount();
    }
}
// ReentrantReadWriteLock.Sync
final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c != 0) {// 可能有写锁或读锁被获取了
        int w = exclusiveCount(c);
        // w == 0表示,如果有线程获取了读锁,就直接失败吗(为什么)不是同一个线程可以直接获取读锁和写锁吗?如果它之前已经获取了读锁,现在再来获取写锁会直接失败吗?
        // 或者有线程获取了写锁,但是只要不是当前线程,就直接失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    if (!compareAndSetState(c, c + 1))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

写锁与读锁结构也基本一致,自己实现了tryLock方法,流程与tryAcquire基本一致,这里不细讲。

读写锁的同步队列

读写锁虽然持有了两把锁,但是只有一个Sync,即只有一个同步队列。

那只有一个同步队列,是怎么管理独占节点和共享节点的阻塞和唤醒的呢?

获取锁在上面都讲了,现在讲讲读写锁是怎么释放的。

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;
}

这是写锁的释放,总体来说比较简单。

看看当前线程还持有多少个锁,如果释放完以后,还持有0个锁,就将当前独占的线程设为空。

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;
    }
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}

实例

构建几个场景:

场景一:

读+读+写(这里读、写分别代表申请读写的线程,按+的先后顺序,表示线程申请读写锁的时间顺序,比如在这里就代表,线程1申请了读锁,之后,线程2申请读锁,线程3申请写锁),这时候同步队列应该是什么样的呢?

public class LockExample {
    public void testReadWriteLock() {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        CountDownLatch countDownLatch = new CountDownLatch(3);
        t1ReadLock(readWriteLock,countDownLatch,0,3);
        t2WriteLock(readWriteLock,countDownLatch,2,3);
        t3ReadLock(readWriteLock,countDownLatch,1,3);
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {

        }
    }
}

以下为线程对应的工作,在所有场景中会复用。

 private void t1ReadLock(ReentrantReadWriteLock readWriteLock,CountDownLatch countDownLatch,int startDelay, int duration) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(startDelay);
                } catch (InterruptedException e) {

                }
                System.out.println("thread1 begin fetch readLock");
                readWriteLock.readLock().lock();
                System.out.println("thread1 has fetch readLock");
                try {
                    System.out.println("======= thread1 do things ==========");
                    TimeUnit.SECONDS.sleep(duration);// 休眠过程中不会放开锁
                } catch (InterruptedException e) {

                } finally {
                    System.out.println("now readWriteLock queue:"+readWriteLock.getQueueLength());
                    System.out.println("======= thread1 do things end ,unlock readLock ==========");
                    readWriteLock.readLock().unlock();
                    countDownLatch.countDown();
                }

            }
        });
        thread1.start();


    }

    private void t2WriteLock(ReentrantReadWriteLock readWriteLock,CountDownLatch countDownLatch,int startDelay,int duration) {
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(startDelay);
                } catch (InterruptedException e) {

                }
                System.out.println("thread2 begin fetch writeLock");
                readWriteLock.writeLock().lock();
                try {
                    System.out.println("======= thread2 do things ==========");
                    TimeUnit.SECONDS.sleep(duration);
                } catch (InterruptedException e) {

                } finally {
                    System.out.println("now readWriteLock queue:"+readWriteLock.getQueueLength());
                    System.out.println("======= thread2 do things end,unlock writeLock ==========");
                    readWriteLock.writeLock().unlock();
                    countDownLatch.countDown();
                }
            }
        });
        thread2.start();
    }

    private void t3ReadLock(ReentrantReadWriteLock readWriteLock,CountDownLatch countDownLatch,int startDelay, int duration) {
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(startDelay);
                } catch (InterruptedException e) {

                }
                System.out.println("thread3 begin fetch readLock");
                readWriteLock.readLock().lock();
                System.out.println("thread3 has fetch readLock");
                try {
                    System.out.println("======= thread3 do things ==========");
                    TimeUnit.SECONDS.sleep(duration);
                } catch (InterruptedException e) {

                } finally {
                    System.out.println("now readWriteLock queue:"+readWriteLock.getQueueLength());
                    System.out.println("======= thread3 do things end unLock ReadLock ==========");
                    readWriteLock.readLock().unlock();
                    countDownLatch.countDown();
                }
            }
        });
        thread3.start();
    }

thread1 begin fetch readLock

thread1 has fetch readLock

======= thread1 do things ==========

thread3 begin fetch readLock

thread3 has fetch readLock

======= thread3 do things ==========

thread2 begin fetch writeLock

now readWriteLock queue:1

======= thread1 do things end ,unlock readLock ==========

now readWriteLock queue:1

======= thread3 do things end unLock ReadLock ==========

======= thread2 do things ==========

now readWriteLock queue:0

======= thread2 do things end,unlock writeLock ==========

从结果可以看出,读+读+写的时候,两个读锁可以并行执行,写锁需要等两个读锁执行完后才能获取锁。看同步队列中,线程数目为1也可以证实这一点。

从代码逻辑来看:

获取写锁的前提是,当前没有任何线程持有读锁,且写锁的获取者为当前线程,或没有线程获取到写锁。

获取读锁的前提是,除了当前线程,其他线程都没有获取到写锁。

所以读+读+写,最终的结论是(读,读+写)

场景二:

读+写+读

public void testReadWriteLock() {
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    CountDownLatch countDownLatch = new CountDownLatch(3);
    t1ReadLock(readWriteLock,countDownLatch,0,3);
    t2WriteLock(readWriteLock,countDownLatch,1,3);// 只调整了写锁和读锁的开始时间
    t3ReadLock(readWriteLock,countDownLatch,2,3);
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {

    }
}

thread1 begin fetch readLock

thread1 has fetch readLock

======= thread1 do things ==========

thread2 begin fetch writeLock

thread3 begin fetch readLock

now readWriteLock queue:2

======= thread1 do things end ,unlock readLock ==========

======= thread2 do things ==========

now readWriteLock queue:1

======= thread2 do things end,unlock writeLock ==========

thread3 has fetch readLock

======= thread3 do things ==========

now readWriteLock queue:0

======= thread3 do things end unLock ReadLock ==========

 

神奇了!如果按场景一的理解,这时候的结果就应该跟场景一的一致。但现在线程3却跟线程2一起阻塞了,这是为什么呢?

原因是,读写锁默认是非公平锁。

非公平锁中重写了readerShouldBlock方法。其中readerShouldBlock方法中

判断,如果下一个要唤醒的节点为写锁,则阻塞当前读锁的获取(至于为什么这么做,可能是因为担心饥饿问题把,毕竟读锁是共享的,如果一直都让读锁共享,很容易导致写锁一直没有机会获取)。

所以,对于读+写+读,最终的顺序就是(读+写+读)

 

附+测试:

对于读+写+读+读,后面两个读线程会并发执行吗?

public void testReadWriteLock() {
    final int DURATION_TIME = 5;
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    CountDownLatch countDownLatch = new CountDownLatch(4);
    t1ReadLock(readWriteLock,countDownLatch,0,DURATION_TIME);
    t2WriteLock(readWriteLock,countDownLatch,1,DURATION_TIME);
    t3ReadLock(readWriteLock,countDownLatch,2,DURATION_TIME);
    t4ReadLock(readWriteLock,countDownLatch,3,DURATION_TIME);
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {

    }
}

thread1 begin fetch readLock

thread1 has fetch readLock

======= thread1 do things ==========

thread2 begin fetch writeLock

thread3 begin fetch readLock

thread4 begin fetch readLock

now readWriteLock queue:3

======= thread1 do things end ,unlock readLock ==========

======= thread2 do things ==========

now readWriteLock queue:2

======= thread2 do things end,unlock writeLock ==========

thread3 has fetch readLock

======= thread3 do things ==========

thread4 has fetch readLock

======= thread4 do things ==========

now readWriteLock queue:0

now readWriteLock queue:0

======= thread3 do things end unLock ReadLock ==========

======= thread4 do things end unLock ReadLock ==========

 

这里后面thread3和thread4是并行执行完的。因为线程2释放的时候,会去释放线程3。线程3在执行doAcquireShared方法的时候,会释放后继节点,即线程4。

场景三:

写+写+读

这其实很简单,写线程是独占锁,所以最终顺序就是写+写+读

public void testReadWriteLock() {
    final int DURATION_TIME = 4;
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    CountDownLatch countDownLatch = new CountDownLatch(3);
    t1ReadLock(readWriteLock,countDownLatch,2,DURATION_TIME);
    t2WriteLock(readWriteLock,countDownLatch,0,DURATION_TIME);
    t5WriteLock(readWriteLock,countDownLatch,1,DURATION_TIME);
    
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {

    }
}

thread2 begin fetch writeLock

======= thread2 do things ==========

thread5 begin fetch writeLock

thread1 begin fetch readLock

now readWriteLock queue:2

======= thread2 do things end,unlock writeLock ==========

======= thread5 do things ==========

now readWriteLock queue:1

======= thread5 do things end,unlock writeLock ==========

thread1 has fetch readLock

======= thread1 do things ==========

now readWriteLock queue:0

======= thread1 do things end ,unlock readLock ==========

 

场景四:

同一个线程,先获取写锁再获取读锁

private void writeToReadLock(ReentrantReadWriteLock readWriteLock) {
    Thread thread = new Thread(()->{
        readWriteLock.writeLock().lock();
        System.out.println("do write things");
        readWriteLock.readLock().lock();
        try {

        } finally {
            System.out.println("unlock writeLock");
            readWriteLock.writeLock().unlock();
            System.out.println("unlock readLock");
            readWriteLock.readLock().unlock();
        }
    });
    thread.start();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值