ReentrantReadWriteLock源码分析

ReentrantReadWriteLock

读写锁,本身也是有AQS实现的,主要分为独占与共享两种状态,分别对应读锁与写锁,读锁是一把共享锁,多个线程都可以共享获取到一把锁。写锁时独占锁,统一时刻只能有一个线程获取到写锁。默认创建的是非公平模式的锁。
我们知道AQS中使用state状态来区分是否获取到锁,在独占模式下如果state == 0说明没有获取到独占锁,state != 0说明有其他线程获取到独占锁。在共享模式下,如果state < 0代表没有获取到共享锁,在CountDownLatch中如果state 不等于 0说明获取不到共享锁,在Semaphore中state小于1获取不到共享锁。

  • 独占模式来说,0 代表可获取锁,1 代表锁被别人获取了,重入情况下会往上加
  • 共享模式下,每个线程都可以对 state 进行加减操作

state读写状态

在读写锁中将state值分为高16位与敌16位进行设置读写锁的次数
在这里插入图片描述
获取写状态:

state&0x0000FFFF:将高16位全部抹去

获取读状态:

state>>>16:无符号补0,右移16位

(3)写状态加1:

 state+1

(4)读状态加1:

state +(1<<16)

原则

读读共享、读写互斥、写写互斥

允许写锁降级为读锁,称之为锁降级。
不允许读锁升级升级到写锁,会造成死锁。

demo及源码分析

public static void main(String[] args) {

    ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    new Thread(()->{
        readLock.lock();
        System.out.println("线程1获取到读锁!");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readLock.unlock();
            System.out.println("线程1读锁释放!");
        }
    },"线程1").start();

    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    new Thread(()->{
        readLock.lock();
        System.out.println("线程2获取到读锁!");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readLock.unlock();
            System.out.println("线程2读锁释放!");
        }
    },"线程2").start();

    new Thread(()->{
        writeLock.lock();
        System.out.println("线程3获取到写锁!");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            writeLock.unlock();
        }
    },"线程3").start();

}

结果

线程1获取到读锁!
线程2获取到读锁!
线程2读锁释放!
线程1读锁释放!
线程3获取到写锁!

ReentrantReadWriteLock类信息

//这些属性主要时将state分为高低位分别计算,高16位位读锁
//低16位为写锁
static final int SHARED_SHIFT   = 16;
//1 00000000 00000000
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
//读锁最多次数
//15个1
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
//用于计算写锁次数
//15个1
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//读锁次数 向右移动16位,获取高位读锁次数
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
//写锁次数,将高16位置为0,取低位写锁次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
//用于计算当前线程的读锁次数与id
static final class HoldCounter {
    int count = 0;
    final long tid = getThreadId(Thread.currentThread());
}
//ThreadLocal子类,重写默认值方法,就是说没有set时get取的默认值
static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}
//当前线程的ThreadLocal
private transient ThreadLocalHoldCounter readHolds;
//最后一个线程的缓存数据
private transient HoldCounter cachedHoldCounter;
//第一个获取读锁的线程以及次数
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;

Sync() {
    readHolds = new ThreadLocalHoldCounter();
    setState(getState()); 
}

读锁

lock

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

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        //如果获取锁失败将当前线程加入到阻塞队列中并中断当前线程,等待前置节点唤醒
        doAcquireShared(arg);
}

tryAcquireShared
尝试获取读锁

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 &&
        //cas成功,将读锁次数的高16位加一
        //就是加1 00000000 0000000,1后面16个0 高16位的1
        compareAndSetState(c, c + SHARED_UNIT)) {
        //代表当先线程是第一个获取到读锁
        if (r == 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();
            //什么时候rh.count == 0
            else if (rh.count == 0)
                 //记录次数
                readHolds.set(rh);
            //当前线程获取读锁的次数加加
            rh.count++;
        }
        //返回1 获取到读锁 就是共享锁
        return 1;
    }
    return fullTryAcquireShared(current);
}
  • 以上逻辑我们都已经注释
  • rh.count什么时候等于0,如果当前线程就是最后一个线程缓存的线程信息,并且已经释放锁后再次获取锁操作,才有可能进入到此处逻辑

fullTryAcquireShared

final int fullTryAcquireShared(Thread current) {
   //什么时候进入到这个方法cas失败,如果有两个线程来获取读锁,其中一个cas成功获取到锁,第二个进入到此方法
   //如果队列中第一个是写锁,我们也进入到此方法,我们不想与他枪锁,但是我们是来重入读锁的
   HoldCounter rh = null;
   //死循环 + cas 多线程枪锁
   for (;;) {
       //获取状态
       int c = getState();
       //如果有线程获取到写锁,那此时读锁肯定获取不到了,进入到阻塞队列排队
       if (exclusiveCount(c) != 0) {
            //并且不是当前线程,表明获取读锁失败,加入到队列中排队获取锁
           if (getExclusiveOwnerThread() != current)
               return -1;
       } else if (readerShouldBlock()) {
            //进入到这里说明没有线程获取到写锁
            //并且队列中第一个是写锁并正在等待获取锁
           if (firstReader == current) {
                //直接进入下面的cas
           } 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");
         //cas
       if (compareAndSetState(c, c + SHARED_UNIT)) {
           //如果读锁 == 0 说明当前是第一个获取读锁的线程
           if (sharedCount(c) == 0) {
               firstReader = current;
               firstReaderHoldCount = 1;
           } else if (firstReader == current) {
               firstReaderHoldCount++;
           } else {
               //下面几段代码就是cachedHoldCounter设置为当前线程
               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);
}
public final boolean releaseShared(int arg) {
    //尝试释放锁
    if (tryReleaseShared(arg)) {
         //如果尝试释放锁成功
         //唤醒后续节点
        doReleaseShared();
        return true;
    }
    return false;
}
protected final boolean tryReleaseShared(int unused) {
 //获取当前线程
   Thread current = Thread.currentThread();
   //如果第一个获取读锁的线程等于当前线程
   if (firstReader == current) {
       //如果第一个获取读锁的线程没有重入,只有一次
       if (firstReaderHoldCount == 1)
           //把第一个获取读锁线程置空
           firstReader = null;
       else
           //如果第一个获取读锁的线程有重入,则执行减减操作
           firstReaderHoldCount--;
   } else {
       //如果当前线程不是第一个获取读锁的线程
       //判断是不是最后一个
       HoldCounter rh = cachedHoldCounter;
       if (rh == null || rh.tid != getThreadId(current))
          //获取当前线程获取读锁的次数与线程id
           rh = readHolds.get();
       int count = rh.count;
       //如果次数小于等于1,移除当前线程的数据,次数与id
       if (count <= 1) {
           readHolds.remove();
           if (count <= 0)
               throw unmatchedUnlockException();
       }
       --rh.count;
   }
   for (;;) {
       //获取状态
       int c = getState();
       //读锁减减,高16位减一
       int nextc = c - SHARED_UNIT;
       //cas成功后
       if (compareAndSetState(c, nextc))
            //返回读锁是否等于0,读锁是否全部都已经释放
           return nextc == 0;
   }
}

写锁

写锁比较简单

public void lock() {
    sync.acquire(1);
}
public final void acquire(int arg) {
   //尝试获取锁
   if (!tryAcquire(arg) &&
   //获取写锁失败加入到阻塞队列
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
       selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
    //获取当前线程
    Thread current = Thread.currentThread();
    int c = getState();
    //写锁次数,就是为了判断是否有线程获取到写锁
    int w = exclusiveCount(c);
    if (c != 0) {
        //有线程获取到读锁,但是没有线程获取到写锁
        // c != 0 && w == 0: 写锁可用,但是有线程持有读锁(也可能是自己持有)
        // c != 0 && w !=0 && current != getExclusiveOwnerThread(): 其他线程持有写锁
        //直接返回false 排队去吧
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
         //写锁重入次数是否大于最大值
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //设置写锁状态,此时代表锁重入
        setState(c + acquires);
        return true;
    }
    //没有读锁 没有写锁 cas 抢锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

unlock

public void unlock() {
    sync.release(1);
}
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

protected final boolean tryRelease(int releases) {
     //是否是当前线程,不是当前线程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //状态减
    int nextc = getState() - releases;
    //写锁是否等于0
    boolean free = exclusiveCount(nextc) == 0;
    //如果等于0写锁释放,获取写锁线程置空
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值