Java多线程系列(十一)—ReentrantReadWriteLock源码分析

Java多线程系列(十一)—ReentrantReadWriteLock源码分析

ReentrantReadWriteLock读写锁是基于AQS的独占模式和共享模式实现的,是为了优化ReentrantLock互斥锁的并发性能;

个人主页:tuzhenyu’s page
原文地址:Java多线程系列(十一)—ReentrantReadWriteLock源码分析

(0) ReentrantReadWriteLock读写锁的使用

class ReadThread extends Thread{
    private ReentrantReadWriteLock lock;
    public ReadThread(String name,ReentrantReadWriteLock lock){
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" try to lock");
        lock.readLock().lock();
        System.out.println(Thread.currentThread().getName()+" lock successfully");
        try {
            sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        lock.readLock().unlock();
        System.out.println(Thread.currentThread().getName()+" unlock successfully");
    }
}

class WriteThread extends Thread{
    private ReentrantReadWriteLock lock;
    public WriteThread(String name,ReentrantReadWriteLock lock){
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" try to lock");
        lock.writeLock().lock();
        System.out.println(Thread.currentThread().getName()+" lock successfully");
        try {
            sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
        lock.writeLock().unlock();
        System.out.println(Thread.currentThread().getName()+" unlock successfully");
    }
}

public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        ReadThread r1 = new ReadThread("r1",lock);
        ReadThread r2 = new ReadThread("r2",lock);
        WriteThread w1 = new WriteThread("w1",lock);
        WriteThread w2 = new WriteThread("w2",lock);
        r1.start();
        r2.start();
        w1.start();
        w2.start();
    }
}

运行结果

r1 try to lock

r1 lock successfully

r2 try to lock

r2 lock successfully

w1 try to lock

w2 try to lock

r1 unlock successfully

r2 unlock successfully

w1 lock successfully

w1 unlock successfully

w2 lock successfully

w2 unlock successfully

(1) ReentrantReadWriteLock读写锁的常用方法

  • 读写锁的类属性
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
  • 读写锁的常用方法就是读锁和写锁的获取方法
 // 返回用于读取操作的锁。
ReentrantReadWriteLock.ReadLock readLock()
// 返回用于写入操作的锁。 ReentrantReadWriteLock.WriteLock writeLock()

(2) ReentrantReadWriteLock读写锁的类结构

  • ReentrantReadWriteLock有五个内部类,五个内部类之间也是相互关联的.

    • 公平锁和非公平锁(三个):Sync类继承AQS类,有两个子类FairSync公平锁和NonfairSync非公平锁

    • 读锁和写锁(两个):ReadLock实现了Lock接口、WriteLock也实现了Lock接口
      Java多线程系列(十一)—ReentrantReadWriteLock源码分析_第1张图片

  • Sync类内存在两个内部类,分别为HoldCounter和ThreadLocalCounter;

    • HoldCounter主要是与线程绑定用作计数,与读锁配套使用;HoldCounter主要有两个属性,count和tid,其中count表示某个读线程重入的次数,tid表示该线程的tid字段的值,该字段可以用来唯一标识一个线程;
    • ThreadLocalCounter重写了ThreadLocal的initialValue()方法,ThreadLocal类可以将线程与对象相关联。在没有进行set的情况下,get到的均是initialValue方法里面生成的那个HolderCounter对象。
      Java多线程系列(十一)—ReentrantReadWriteLock源码分析_第2张图片
// 计数器

        static final class HoldCounter {

            // 计数

            int count = 0// 获取当前线程的TID属性的值

            final long tid = getThreadId(Thread.currentThread());

        }
// 本地线程计数器

        static final class ThreadLocalHoldCounter

            extends ThreadLocal<HoldCounter> {

            // 重写初始化方法,在没有进行set的情况下,获取的都是该HoldCounter值

            public HoldCounter initialValue() {

                return new HoldCounter();

            }

        }

(3) ReentrantReadWriteLock语义的实现

  • 读写锁的实例化,默认是非公平锁

public ReentrantReadWriteLock() {
    this(false);
}

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; }
  • 读锁的加锁和释放锁都是基于Sync类方法acquireShared(1)和releaseShared(1)
public void lock() {
    sync.acquireShared(1);
}

public void unlock() {
    sync.releaseShared(1);
}
  • 写锁的加锁和释放锁都是基于Sync类方法acquire(1)和release(1)
public void lock() {
    sync.acquire(1);
}

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

Sync类的核心方法

  • sharedCount()方法从state中获取读锁的数量,state的高16位保存着读锁数量,将state无符号右移16位即可获取读锁数量

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
  • exclusiveCount()方法从state中获取写锁的数量,state的低16位保存着写锁数量,获取低16位的值即为写锁数量

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

获取写锁

  • 写锁调用lock()方法

public void lock() {
    sync.acquire(1);
}
  • 调用AQS的acquire()方法,尝试获取写锁,如果获取失败则阻塞线程放入同步队列尾部;

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • tryAcquire()方法尝试获取写锁

    • 首先获取state值和写锁数量,如果state为0则表示没有线程占用读写锁,则判断公平模式和非公平模式是否获取该锁;如果state不为0则表示存在读锁或者写锁;

    • 如果state不为0,判断写锁数目是否为0和当前线程是否已经独占该锁,如果当前线程已经独占该锁,则修改state的写锁重入次数;


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

Java多线程系列(十一)—ReentrantReadWriteLock源码分析_第3张图片

  • 调用writerShouldBlock()方法判断公平模式或者非公平模式下是否可以直接获取锁

非公平锁

final boolean writerShouldBlock() {
    return false; // writers can always barge
}

公平锁

final boolean writerShouldBlock() {
    return hasQueuedPredecessors();
}

写锁的释放

  • 释放写锁
public void unlock() {
    sync.release(1);
}
  • 调用AQS的release()方法尝试释放写锁,释放成功后唤醒同步队列头的一个等待线程;

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
  • tryRelease()方法释放写锁

    • 调用isHeldExclusively()方法判断当前线程是否为独占,写锁拥有的线程肯定是独占,如果不是则抛出异常;

    • 修改state的值释放写锁

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;
}
  • 调用isHeldExclusively()方法判断当前线程是否为独占
protected final boolean isHeldExclusively() {
    return getExclusiveOwnerThread() == Thread.currentThread();
}

Java多线程系列(十一)—ReentrantReadWriteLock源码分析_第4张图片

读锁的获取

  • 调用lock()方法获取读锁
public void lock() {
    sync.acquireShared(1);
}
  • 调用AQS的tryAcquireShared()方法尝试获取读锁
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
  • acquireShared()首先会通过tryAcquireShared()来尝试获取锁,如果获取成功则返回;否则调用doAcquireShared()循环尝试获取锁;
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
  • tryAcquireShared()尝试获取共享锁,如果不需要阻塞等待并且共享线程的数目小于上限,则直接通过CAS函数更新“读取锁的共享计数”,以及将“当前线程获取读取锁的次数+1”。
    • 公平锁下判断同步同步队列中是否有线程排队,如果有则需要阻塞加入到同步队列尾部;非公平锁下需要判断同步队列头部是否为写锁线程,如果是则需要阻塞加入到同步队列尾部,用来避免写线程过度饥饿;
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    // 获取“锁”的状态
    int c = getState();
    // 如果“锁”是“互斥锁”,并且获取锁的线程不是current线程;则返回-1。
    //如果当前锁是写锁,则共享锁被阻塞;
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // 获取“读取锁”的共享计数
    int r = sharedCount(c);
    // 如果“不需要阻塞等待”,并且“读取锁”的共享计数小于MAX_COUNT;
    // 则通过CAS函数更新“锁的状态”,将“读取锁”的共享计数+1。
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        // 第1次获取“读取锁”。
        if (r == 0) { 
            firstReader = current;
            firstReaderHoldCount = 1;
        // 如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程
        } else if (firstReader == current) { 
            firstReaderHoldCount++;
        } else {
            // HoldCounter是用来统计该线程获取“读取锁”的次数。
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != current.getId())
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            // 将该线程获取“读取锁”的次数+1。
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}
  • 如果当前线程被读锁占用但是需要等待或者读锁数量达到最大值则调用fullTryAcquireShared()循环获取共享锁,直至获取成功;
    • 在tryAcquireShared函数中,如果下列三个条件不满足(读线程是否应该被阻塞、小于最大值、比较设置成功)则会进行fullTryAcquireShared函数中,它用来保证相关操作可以成功
final int fullTryAcquireShared(Thread current) {
    /*
     * This code is in part redundant with that in
     * tryAcquireShared but is simpler overall by not
     * complicating tryAcquireShared with interactions between
     * retries and lazily reading hold counts.
     */
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

读锁的释放

  • 调用unlock()方法释放读锁
public void unlock() {
    sync.releaseShared(1);
}
  • 调用releaseShared()方法释放读锁,
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
  • 调用tryReleaseShared()方法修改state状态
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){
            return nextc == 0;
    }
}
  • 调用doReleaseShared()方法唤醒同步队列中所有的阻塞线程;
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

总结

  • 在读多于写的情境下,读写锁相比于独占锁具有更高的性能,因为读锁是共享锁,写锁为排他锁;
  • 因为锁的重入性特点,以一个线程可以获取到写锁后继续获取读锁,不能获取读锁后再获取写锁;也就是说写锁可以降级为读锁,读锁不能升级为写锁;
  • 读锁和写锁的实现依赖于AQS同步器的两套锁获取和释放API,分别对应独占模式和共享模式;

你可能感兴趣的:(java)