线程基础知识06- ReentrantReadWriteLock读写锁

同一时刻允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。
所有晚于写操作的读操作都会进入等待状态。

数据结构

  • 内部类提供了读写锁的子类
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** 读锁 */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 写锁*/
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** 同步机制 */
    final Sync sync;
}

独写锁的状态设计

  • 获取当前同步状态,通过位运算,16位以上记录的是读的状态数,16位以下获取的是写的状态数。

  • S&(1 << 16 -1)(S是同步状态值,将高位16位抹去)获取写状态

  • S>>>16 获取读状态

(图片摘自《java并发艺术》)

同步机制Sync

  • 读锁和写锁的锁获取方法和释放方法都在这个类进行实现

独占锁的获取

  • 判断状态是否为0,如果状态为0,判断是同步队列的头节点。如果是则设置独占锁为当前队列,如果不是返回false,增加到同步队列中阻塞;

  • 状态不为0时,独占锁数量>0且独占线程是当前线程,说明当前线程获得了锁;

  • 如果独占锁数量为0,或者独占锁线程不是当前线程,则将当前线程增加到同步队列中。

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;

    protected final boolean tryAcquire(int acquires) {
           
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);//获取独占锁数量
            if (c != 0) {//状态不为0时
                if (w == 0 || current != getExclusiveOwnerThread())//独占锁为空,或者当前线程非独占线程,返回false
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)//超过最大数量失败
                    throw new Error("Maximum lock count exceeded");   
                setState(c + acquires);//更新锁状态
                return true;
            }
            /**
             * 1.同步队列,如果当前节点还有前驱节点。
             * 2.更新锁状态失败
             */ 
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);//设置当前线程为独占线程
            return true;
        }

/** 
 * 返回持有的锁数量
 * 0000 0000 0000 1000  & 1111 1111 1111 1111 = 0000 0000 0000 1000
 */
tatic int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

独占锁的释放

  • 如果当前线程不是独占线程报错

  • 如果独占线程状态码为空,设置独占线程为空

  • 更新状态码

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

共享锁的获取

  • 如果存在独占锁并且独占锁不是当前线程的情况,当前线程加入到队列中;

  • 获取同步队列,如果当前线程是同步队列头节点,则调整状态,并记录当前线程。

  • 如果当前线程不是以上情况,则进行CAS自旋锁获取。

  /**
   * 继承了ThreadLocal,其实质就是将数据保存到当前线程的缓存数据中
   */   
       static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

 protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)//如果独占锁被其他线程获得,返回-1,加入同步队列
                return -1;
            int r = sharedCount(c);//同步队列
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {//如果为同步队列头节点,且小于最大数,通过CAS设置状态码
                if (r == 0) {//如果共享锁为0,则设置首读线程为当前线程,首读线程的次数为1
                    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);//如果不是上面情况,则进行自旋CAS进行处理
        }

/**获取读线程数量*/
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
/**
 * 1.通过自旋CAS进行锁获取
 */
 final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();//获取状态码
                if (exclusiveCount(c) != 0) {//有独占锁的情况
                    if (getExclusiveOwnerThread() != current)//如果独占线程不是当前线程返回-1,加入到同步队列当中,如果独占线程是当前线程就会死锁而造成阻塞
                        return -1;
                } else if (readerShouldBlock()) {//读锁阻塞状态
                    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)//当前查询次数为0,放入到同步队列中
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {//通过CAS替换当前状态,以下的操作和上面类似
                    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; // 缓存进行释放
                    }
                    return 1;//返回获取锁成功
                }
            }
        }

/**
 * 作用:同步队列中的头节点是独占节点,并且头节点的后继节点也是独占节点
 */
 final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }


共享锁的释放

  • 判断当前线程是不是首读线程,如果是且只读了一次,则首读线程制空;

  • 如果首读次数大于1,说明多次嵌套,count-1

  • 如果不是首读线程,就判断当前线程缓存中的count数进行-1;

  • 通过CAS自旋进行状态码更改,如果更改后状态码为0,则表明完全释放

  protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {//如果是首个read线程
                if (firstReaderHoldCount == 1)//只访问一次,制空;否则将访问数减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 (;;) {//CAS自旋进行锁释放
                int c = getState();
                int nextc = c - SHARED_UNIT;//读锁状态码-1
                  /**
                   * 释放读锁不影响读操作
                   * 但是如果同时释放读锁和写锁,写锁一定要先于读操作,防止出现数据不一致情况
                   */      
                if (compareAndSetState(c, nextc))
                    return nextc == 0;//如果==0,说明共享锁完全释放成功了
            }
        }



原文地址:https://www.cnblogs.com/perferect/p/13628187.html