并发编程--读写锁ReadWriteLock和ReentrantReadWriteLock写锁与读锁(二)

时间:2023-02-04 20:47:44

在上一篇博客并发编程--读写锁ReadWriteLock和ReentrantReadWriteLock(一)中我们简单介绍了一下读写锁的相关知识,接下来来我们介绍一下读锁的实现机制,简单的来说读锁就是一个独占锁,如果看过ReentrantLock相关的知识,应该会对独占锁的实现有一些简单的理解,简单来说独占锁的实现是当锁标识位state为0时,当前线程获取锁,并将state进行加一操作,其他线程来获取锁时会发现state此时不为0,则将当前线程添加到FIFO队列中,并将线程进行阻塞,线程释放锁时会将state进行减一操作,并唤醒FIFO队列中阻塞的线程来竞争锁。

接下来我们看看读锁是如何实现的。 lock的操作是如果获取锁则将锁标识位state进行加一操作。

public void lock() {
sync.acquire(1);//锁标识位加一
}

acquire中会尝试获取锁,如果获取锁失败则将线程添加到FIFO队列中挂起

 public final void acquire(int arg) {//tryAcquire(arg) 尝试获取锁,如果获取锁失败调用acquireQueued将线程添加FIFO队列中并挂起        if (!tryAcquire(arg) &&            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))            selfInterrupt();    }
在tryAcquire中判断是否可以获取锁,这里有一点不同的是如果一个读线程获取了锁,则锁标识位state加上65536
 protected final boolean tryAcquire(int acquires) {//获取当前线程            Thread current = Thread.currentThread();//获取锁标识位值            int c = getState();//与65535进行与运算,如果此时读线程获取锁则c=65536,此时w=0            int w = exclusiveCount(c);            if (c != 0) {                // (Note: if c != 0 and w == 0 then shared count != 0)//当此时有写锁或者读锁是都返回false                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值为65536,w=exclusiveCount(c)获取的值为0,则返回false,当两个读线程竞争锁时,w=exclusiveCount(c) 值w=c,但current != getExclusiveOwnerThread()为true则返回false。其实读锁是一个独占锁,当state值不为0且运行线程不是当前线程则就无法获取锁。

释放写锁:

public void unlock() {            sync.release(1);//将state值进行减一操作        }

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;    }
 protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())                throw new IllegalMonitorStateException();//将state进行减release操作            int nextc = getState() - releases;//当nextc等于0时将当前执行线程设置为null            boolean free = exclusiveCount(nextc) == 0;            if (free)                setExclusiveOwnerThread(null);//修改锁标识位            setState(nextc);            return free;        }
这样就完成了锁的释放。

接下来我们来看看读锁是如何获取锁的,并且读锁是共享锁,我们来了解一下读锁是如何实现共享锁的。

调用lock方法获取锁

 public void lock() {//获取锁            sync.acquireShared(1);        }
acquireShared会尝试获取锁,如果无法获取锁时则将当前线程添加到FIFO队列中,并将线程阻塞

   public final void acquireShared(int arg) {//尝试获取锁        if (tryAcquireShared(arg) < 0)//将线程添加到FIFO队列中            doAcquireShared(arg);    }

tryAcquireShared中的操作是如果获取共享锁的话会将state设置为state = state +65536,这样下一个读锁过来的时候state>>>16值一般会小于65535的,这样就实现了读锁不会被读锁阻塞了。exclusiveCount(0)的操作是c&65535,65536&65535等于0则是可以获取读锁,不然就是写锁,阻塞当前阻塞。

protected final int tryAcquireShared(int unused) {             Thread current = Thread.currentThread();//获取锁标识位值            int c = getState();//如果是不存在锁exclusiveCount(c)等于0,如果是存在读锁exclusiveCount(c)65536&65535等于0,如果存在写锁exclusiveCount(c)返回值不为0            if (exclusiveCount(c) != 0 &&                getExclusiveOwnerThread() != current)                return -1;            int r = sharedCount(c);            if (!readerShouldBlock() &&                r < MAX_COUNT &&//最终设置锁标识位位state=state+65536,这个是与写锁不同的地方                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);        }

锁的释放

读锁在设置锁时将state设置为state=state+65536,那样释放锁时state设置为state = state-65536,这样就完成了锁的释放操作。