ReentrantWriteLock原理

目录

前言

写锁的获取

读锁获取

 问:当你如果是不公平模式下,写锁产生了饥饿现象怎么办?

写锁unlcok


 

前言

众所周知,ReentrantReadWriteLock是读写锁,它拥有独占锁和共享锁两种模型。

如果是读操作:上共享锁,因为是可以同时访问的;

而如果是写操作:上独占锁,写写或者读写都得上锁,等它完成释放资源后才能其他的访问;

其实ReentrantLock与之一样,ReentrantWriteLock是一把锁

写锁的获取

其实会尝试获取三次,这个for循环里会判断两次,如果还是没有获取到锁,那么就会被park进入阻塞;

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

与ReentrantLock一样的流程,先尝试获取锁,如果失败将线程封装为Node,加入到阻塞队列,调用acquireQueued方法实现阻塞当前线程(acquireQueued方法中会得到当前节点的前序节点,然后再次判断是否拿到锁资源,没拿到就会进入阻塞park掉(不对,会再次循环一次如果还没有才会park));

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();
    //获取写锁状态(w>0表示已经有线程获取写锁)
    int w = exclusiveCount(c);
  
//  因为state分了高16位与低16位给到读写锁,所以只能说明获取了锁
    if (c != 0) {

       //1.写锁=0表示加的读锁,因为读写互斥,直接返回false,2.加了写锁,看是不是自己加的,如果不是,就获取失败    
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
       
       //不能超过最大值,重入次数 
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        
        //表示发生写重入返回true
        setState(c + acquires);
        return true;
    }
   
//  说明没有加锁c=0,别的线程加锁(writeShouldBlock会判断是否是公平模式,若为公平就会对队列进行判断,看后继是否有节点,因为公平模式是轮流来的)
    if (writerShouldBlock() ||
         //cas操作将state改为1
        !compareAndSetState(c, c + acquires))
        return false;
  //当前线程为拥有者
    setExclusiveOwnerThread(current);
    return true;
}

读锁获取

这些都是AQS同步器中的方法 

  public void lock() {
            sync.acquireShared(1);
        }
 public final void acquireShared(int arg) {
// tryAcquireShared会判断是否有写锁占据
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

-1表示失败,0表示成功但是后续节点不会被唤醒,正数表示成功并且后续节点可以被唤醒; 

  protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }

 对比与之前的ReentrantLock,线程被封装的节点是共享状态的而不是独占状态;

  private void doAcquireShared(int arg) {
  // 包装节点,这里包装的用的是共享节点(两个:一个是头节点,一个是封装线程的节点)
        final Node node = addWaiter(Node.SHARED);
        boolean interrupted = false;
        try {

   // 进行遍历,跟之前的ReentrantLock有相似之处
            for (;;) {
       // 得到前序节点 :判断是否是头节点
                final Node p = node.predecessor();
           
             //如果是头节点,说明后续节点也就是当前node老二有资格被唤醒
                if (p == head) {

                //会进行一次尝试,看读锁是否加锁成功,成功返回1(tryAcquiredShared是读写锁中实现加锁的方法)
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {

                        //因为此时老二已经被唤醒了,所以老二此时要做头节点
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        return;
                    }
                }
                
                //第一次调用shouldParkAfterFailedAcquire目的是:将前序节点状态设置为-1,方便唤醒当前节点,此时还没有park,第二次如果尝试还是没有获取锁,就会park;
                if (shouldParkAfterFailedAcquire(p, node))

                     //parkAndCheckInterrupt()执行的是一个唤醒流程
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        } finally {
            if (interrupted)
                selfInterrupt();
        }
    }

重新设置头节点 

  private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
       // 设置头节点
        setHead(node);
       
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {

           //得到下一个节点
            Node s = node.next;

           //判断节点是否为null,是否是共享节点,如果是共享节点的话,就会被唤醒,他是会被连着一起的,但是如果是独占锁,就不会执行器doReleaseShared
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

 tryReleasedShared:将当前节点后续节点状态进行修改(-1——>0)

 目的:如果说后续节点状态为-1,那么如果其他线程过来就会对其进行干扰,因为头节点也为-1了,老二都没被唤醒,其他线程过来就会造成干扰,让老二唤醒它们;——>然后里面有个unparkSuccessor()方法

private void doReleaseShared() {
      
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {

                 //cas操作将状态进行修改
                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    //唤醒,状态回变为-1
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

 t2,t3线程进行竞争,t2因为加的是读锁所以会是shared,t4加的是写锁,所以是EX;

 问:当你如果是不公平模式下,写锁产生了饥饿现象怎么办?

为什么会产生饥饿现象?

因为你不公平模式,所以会有个竞争,如果在队列中写锁在第一个节点,读锁在写锁后面,

读锁为重入锁的话,那么就会导致写锁很难被获取到,就会产生一个饥饿现象;

目前思路:

可以设置为公平模式;

写锁unlcok

 AQS中的抽象方法 :

public void unlock() {
            sync.release(1);
        }
 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
               //进行唤醒,让下一个节点唤醒,唤醒t2线程,之前的t1线程在tryRelease被释放
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
  protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

对应在读写锁中实现

tryRelease:目的就是将当前锁进行释放,比如t1线程

    protected final boolean tryRelease(int releases) {//1
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();

            //原来线程状态-1
            int nextc = getState() - releases;

            //进行判断,是否减为0
            boolean free = exclusiveCount(nextc) == 0;

            //当-为0
            if (free)
                // 将当前锁资源拥有者设置为null
                setExclusiveOwnerThread(null);

              //并且重新设置线程状态
            setState(nextc);
            return free;
        }

 读写锁中实现读锁获取的方法tryAcquireShared()——>继承了AQS,实现了它的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 &&
                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 != LockSupport.getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

读取锁unlock()

读写锁中调用同步器中releaseShared()方法 

   public void unlock() {
            sync.releaseShared(1);
        }
 public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

 然后来看看读写锁中的释放锁tryReleaseShared:

  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 != LockSupport.getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                   
                    return nextc == 0;
            }
        }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fairy要carry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值