多线程8 读写锁ReentrantReadWriteLock加解锁

2021-01-29 11:07:29 浏览数 (1)

读锁不可以升级,写锁可以降级?

读锁是可并行的,写锁是串行的,那么如果多个读锁并行执行,遇到升级语句,就会出现死锁,比如t1要升级,那么就要等t2释放锁,而t2正好也在当t1释放锁。

加锁:

代码语言:javascript复制
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
代码语言:javascript复制
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
    Thread current = Thread.currentThread();
// 线程计数
    int c = getState();
// 获取独占锁,就是写锁
    int w = exclusiveCount(c);
// 线程计数!=0,表示重入;这里有两种:读锁,写锁
    if (c != 0) {
//  如果是读锁,或者当前线程并非加锁线程,返回false,就会进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg))获取锁
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
// 判断写锁的可重入次数是否超过MAX_COUNT,超过抛异常 
        if (w   exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
// 到这里,确定是写锁且当前线程已持有锁,重入锁 1
        setState(c   acquires);
        return true;
    }
// 走到这里,表示c=0,即没有写锁,也没有读锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c   acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}
代码语言:javascript复制
final boolean writerShouldBlock() {
// 这个方法就是上一篇锁讲过的,查询是否有线程等待,有就排队
    return hasQueuedPredecessors();
}
代码语言:javascript复制
if (writerShouldBlock() ||
// 设置线程计数
        !compareAndSetState(c, c   acquires))
        return false;
代码语言:javascript复制
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
// 然后进入这个方法,上一篇讲过
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

这里就是,队列里排队获取锁,和reentrantLock一样。

读锁:

代码语言:javascript复制
public void lock() {
    sync.acquireShared(1);
}
代码语言:javascript复制
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
代码语言:javascript复制
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
// 判断是否独占锁,是否当前线程;这里如果出现独占锁,那么该线程就返回-1,去排队
// 为什么呢?因为在读锁加锁的时候,也可能出现写锁进来,如果写锁进来了,写锁是排他锁,独占一把锁,那么读锁也要去排队
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
// 非独占锁(写锁),获取计数
    int r = sharedCount(c);
// 没有线程等待,
    if (!readerShouldBlock() &&
// 且读锁计数小于最大值,
        r < MAX_COUNT &&
// 且加锁成功
        compareAndSetState(c, c   SHARED_UNIT)) {
// 读锁计数为0,没有读锁,第一个线程进来
        if (r == 0) {
// 就给第一个线程对象赋值,
            firstReader = current;
            firstReaderHoldCount = 1;
// 如果说第一个线程对象等于当前线程对象,就是重入锁
        } else if (firstReader == current) {
// 那么第一个线程内部计数 1
            firstReaderHoldCount  ;
// 如果以上条件都否定,即不是第一个线程,也不是重入锁
        } else {
// 第一次进来,重入锁计数对象 = null
            HoldCounter rh = cachedHoldCounter;
// 当前缓存中还没有,或者是第二次进来,rh不会空,那么判断rh的线程id是否和当前线程id相同,不同则表示其他线程进入
            if (rh == null || rh.tid != getThreadId(current))
// 拿到缓存的重入锁对象:如果是同一个线程进入,就返回那个线程的缓存计数对象,如果是其他线程,就会初始化一个返回
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
// 缓存对象计数 1
            rh.count  ;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

当前线程可重入锁的数量是内部是线程变量实现;

它自己本身保存了一份ThreadLocalMap对象,用于保存重入锁的线程

代码语言:javascript复制
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
// 有当前线程
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
// 拿不到就初始化一个
    return setInitialValue();
}

假设第三个线程:

代码语言:javascript复制
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
// 判断是否独占锁,是否当前线程;这里如果出现独占锁,那么该线程就返回-1,去排队
// 为什么呢?因为在读锁加锁的时候,也可能出现写锁进来,如果写锁进来了,写锁是排他锁,独占一把锁,那么读锁也要去排队
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
// 非独占锁(写锁),获取计数
    int r = sharedCount(c);
// 没有线程等待,
    if (!readerShouldBlock() &&
// 且读锁计数小于最大值,
        r < MAX_COUNT &&
// 且加锁成功
        compareAndSetState(c, c   SHARED_UNIT)) {
// 读锁计数为0,没有读锁,第一个线程进来
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount  ;
        } else {
// 第三次rh对象不为空
            HoldCounter rh = cachedHoldCounter;
// rh != null,但tid不同 判断为true
            if (rh == null || rh.tid != getThreadId(current))
// 从缓存对象中获取到缓存对象;存在返回,不存在初始化
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
// 缓存对象计数 1,这时缓存对象中第三个的计数是1
            rh.count  ;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

0 人点赞