15-并发类AQS之读写锁ReentrantReadWriteLock实现

ReentrantReadWriteLock介绍

ReentrantReadWriteLock是读写锁,它维护了一对相关的锁读取锁写入锁,一个用于读操作,另一个用于写操作
读取锁:用于只读操作,这它是“共享锁“,能同时被多个线程获取.
写入锁:用于写入操作,它是“独占锁”,写入锁只能被一个线程锁获取。
允许多个读线程同时读并阻塞写线程,多个写线程只有一个写线程写,并阻塞读线程

在了解了AQS底层后和ReentrantLock是如何通过AQS实现锁后,那么接线来看一下读写锁是如何通过AQS实现的

/**
 * 多个线程读,一个线程写
 */
public class ReadWLock {

    private ReadWriteLock lock = new ReentrantReadWriteLock();
    private Lock re = lock.readLock();
    private Lock wr = lock.writeLock();

    private List<String> list ;


    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void set(String s){
        try{
            wr.lock();
            list.add(s);
            System.out.println("wr,listsize"+list.size());
        }finally {

            wr.unlock();
        }
    }

    public String get(){
        try{
            re.lock();
            if(list.size()>0){
                String s =  list.remove(list.size()-1);
                System.out.println("re,listsize"+list.size());
                return s;
            }
            return "";

        }finally {
            re.unlock();
        }
    }


}
二、ReentrantReadWriteLock基于AQS实现

1、ReentrantReadWriteLock结构
在这里插入图片描述
从这个结构可以看出读写锁比ReentrantLock多了2个ReadLock、 WriteLock类
ReadLock WriteLock这2个类都持有Sync类对象,同时也实现了流程方法,但实际这些流程方法都是调用了Sync类的方法
2 、 调用

private ReadWriteLock lock = new ReentrantReadWriteLock();
private Lock re = lock.readLock();
private Lock wr = lock.writeLock();
private List<String> list ;
public List<String> getList() {
    return list;
}
public void setList(List<String> list) {
    this.list = list;
}
public void set(String s){
    try{
        wr.lock();
        list.add(s);
        System.out.println("wr,listsize"+list.size());
    }finally {
        wr.unlock();
    }
}
public String get(){
    try{
        re.lock();
        if(list.size()>0){
            String s =  list.remove(list.size()-1);
            System.out.println("re,listsize"+list.size());
            return s;
        }
        return "";

    }finally {
        re.unlock();
    }
}

3、写锁调用
写线程获取锁方法lock():

public void lock() {
  sync.acquire(1);//调用sync获取锁的方法
}
public final void acquire(int arg) {
 //获取锁成功返回,失败加入同步队列 阻塞,等待锁释放被唤醒
  if (!tryAcquire(arg) &&
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
       selfInterrupt();
}

写锁中tryAcquire是流程方法,需要在读写锁中实现:

protected final boolean tryAcquire(int acquires) {
     Thread current = Thread.currentThread();
     int c = getState();//获锁状态标志位
     int w = exclusiveCount(c);//取出锁状态的低16位-》记录了写锁个数
     if (c != 0) {//锁状态不为0,说明锁被读线程或写线程占有
         //再判断写锁是否被占有或已被占有被不是当前线程占有 
         //返回false,写线程没有获取锁成功,
         //这里current != getExclusiveOwnerThread()
         //判断当前线程如果占有锁那么也可锁取锁,因为锁是可重入的
         if (w == 0 || current != getExclusiveOwnerThread())
             return false;
         //定锁个数超过最大值,抛出异常    
         if (w + exclusiveCount(acquires) > MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
         //写线程获得锁,并将获取锁次数加1    
         setState(c + acquires);
         return true;
     }
     //锁没有被占用,cas更新锁状态;更新失败返回,
     //加入到同步队列中后,调用acquireQueued方法被阻塞,
     //等待锁释放后被唤醒
     if (writerShouldBlock() ||
         !compareAndSetState(c, c + acquires))
         return false;
     //设置当前线程为占有锁的线程    
     setExclusiveOwnerThread(current);
     return true;
 }

可以看出读写锁中写锁实现方式和ReentrantLock实现方式很像,只是这里是用state的低16位记录了锁的获取次数
4、写锁释放

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;
 }
 //这里没有加锁,是因为当前线程是持有锁的线程,它去释放锁
 protected final boolean tryRelease(int releases) {
      if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        int nextc = getState() - releases; //锁次数-1
        boolean free = exclusiveCount(nextc) == 0;//设置到低16位中
        //如果获取锁次数全部释放完,设置当前持有锁线程为Null
        if (free)
            setExclusiveOwnerThread(null);
        setState(nextc);
        return free;
    }

5、读锁调用
读线程获取锁方法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();
      //取现出低16位不为0(写锁被占用)
      //并且不是当前线程不是占有锁线程 返回,
      //因为有写线程在写入,那么读线程不允计操作
      if (exclusiveCount(c) != 0 && 
          getExclusiveOwnerThread() != current)
          return -1;
      //获取高16位-》记录了获取读锁的线程个数
      int r = sharedCount(c);
       //如果读不应该阻塞并且读锁的个数小于65535,
       //并且可以成功更新状态值,成功
      if (!readerShouldBlock() &&
          r < MAX_COUNT &&
           //没有写线程占锁,那么获锁读锁线程个数加1
          compareAndSetState(c, c + SHARED_UNIT)) {
          if (r == 0) {//之前读线程个数为0
              //将当前线程记录为第一个读线程
              firstReader = current;
               //第1个读线程锁取个数赋值1
              firstReaderHoldCount = 1;        
          } else if (firstReader == current) {
            //第一个读线程又获取锁
              //第1个读线程锁取个数+1
              firstReaderHoldCount++; 
          } else { 
          //当前线程不是第1个读线程,
          //那么它的threadLocal将本线程获取锁次数+1
              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);
  }

6、读锁释放

public void unlock() {
    sync.releaseShared(1);
  }
public final boolean releaseShared(int arg) {
   if (tryReleaseShared(arg)) {//尝试释放读锁
        doReleaseShared();//调用AQS释放共享锁
        return true;
    }
    return false;
} 

读写锁对tryReleaseShared实现

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {//当前线程是第1个读线程
        if (firstReaderHoldCount == 1)//只获取过1次
            firstReader = null; //记录第1个读线程为Null
        else
        //否则第1个读线程获取锁次数减1
            firstReaderHoldCount--; 
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {//只获取1次
            readHolds.remove(); //直接移出threadLocal
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        //否则将该线程threadLocal记录的获取锁次数减1
        --rh.count;
    }
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;//计算出获取读锁线程个数
        //cas设置获取读锁线程个数
        if (compareAndSetState(c, nextc)) 
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}

从上面可以看出,state这个变量,高16位记录了获取读锁线程个数,然后每个读线程通过ThreadLocal记录了每个线程重复获取锁次数;低16位记录写线程获取锁次数。
在这里插入图片描述
读锁:是一个共享锁,会阻塞尝图获取写锁的线程
写锁:本质是一个独占锁和ReentrantLock很像,会阻塞试图获取读锁的线程和其它尝图获取写锁的线程

总结

1.写锁的获取与释放
写锁获取:
写锁是一个支持重进入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当 前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程, 则当前线程进入等待状态。
写锁释放:
写锁的释放与ReentrantLock的释放过程基本类似,每次释放均减少写状态,当写状态为0 时表示写锁已被释放,从而等待的读写线程能够继续访问读写锁,同时前次写线程的修改对 后续读写线程可见。
2.读锁的获取与释放
读锁的获取:
读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问 (或者写状态为0)时,读锁总会被成功地获取,而所做的也只是(线程安全的)将持有读线程锁的个数+1,将本线程记录重入的次数threadLocal变量加1。如 果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已被其他线程 获取,则进入等待状态。

锁降级:

锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读 锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到 读锁,随后释放(先前拥有的)写锁的过程。

写锁降级成读锁
读锁阻塞写锁的,只有所有读锁释放后,写锁才获取,所有写锁可看到新写入的肉容

写锁不能降级
即有写线程、读线程同时运行
写锁写入内容,读线程可能看不到最新写入的线程

lockSupport工具

LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功 能,而LockSupport也成为构建同步组件的基础工具。
LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread) 方法来唤醒一个被阻塞的线程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值