• 聊聊ReentrantLock实现原理


    ReentrantLock 是常用的锁,相对于Synchronized ,lock锁更人性化,阅读性更强

    从LOCK切入

             考虑下面的场景如果有A,B线程,同时去执行lock.lock(Lock lock = new ReentrantLock 为全局属性),当A线程抢到锁以后,此时B线程做了哪些事情

             要知道B线程做了那些事情,就要知道一个类AbstractQueueSynchronizer(简称AQS),它负责将锁竞争失败线程存储起来

      Lock lock =  new ReentrantLock();  
            try{
                lock.lock();
                System.out.println("测试");
            }finally {
              lock.unlock();
            }
    

      

    AQS 分析

         要理解锁的运行原理,至少要了解下面的几个问题

          ①,如何锁定(或者说锁定的标准是什么)

          ②,竞争锁失败,怎么处理

           ③,如何释放锁

          ④,如何唤醒其他线程重新竞争

         针对上面的4个问题,分析一下ReenTrantLock

    如何锁定

     //当执行lock.lock()竞争锁 
     final void lock() {
    //1,A,B线程竞争ReenTrantLock->AQS属性->state属性
    //2,乐观锁的形式更新state,如A线程先将state更新为1,就代表A线程竞争锁成功

    if (compareAndSetState(0, 1))
    //设置锁持有线程 setExclusiveOwnerThread(Thread.currentThread()); else
    //B线程竞争锁失败,执行下面方法 acquire(1); }

     要理解AQS的原理中,理解state的状态值变化很重要

      state=0 ,node节点创建时state=0

           state=-1,后继线程可以考虑阻塞啦,因为后继线程不是下一个需要执行竞争锁的(在添加一个新的节点后,那么的它的pre节点的state会变成-1)

           state = 1,节点失效,如:当tryLock(1000,TimeUnit.MILLISECOND) 超时时,节点状态值

    B线程竞争锁失败,处理方式

      1,尝试重新获取锁(目的

                      1.1,看看前面锁是否释放(先下手为强,假如成功尼,哈哈,满满的求生欲)
                      1.2,查看是否同一个线程竞争锁,考虑重入锁

      2,仍然失败,将B线程封装一个新的node节点,插入一个双向队列的尾部

      3,开始自旋(死循环),直到获取锁为止

        3.1,判断当前线程是否有资格竞争锁

        3.2,无资格或者竞争失败的情况下,考虑是否暂停B线程线程(A线程释放锁的时候,会唤醒B线程)

           从源码角度,来解析一下上面的内容

    public final void acquire(int arg) {
            //尝试获取锁,失败,考虑可重入锁
            if (!tryAcquire(arg) &&
                //addWaiter 添加到双向队列的尾结点
                //acquireQueued,开始自旋(死循环)
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                //如果当前线程在竞争过程中存在中断情况,设置当前线程中断
                selfInterrupt();
        }
    
    protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
        }
    
    final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (compareAndSetState(0, acquires)) {
                        setExclusiveOwnerThread(current);
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires;
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
    
        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
    //判断当前这个节点前节点是否是head节点,是的话,重新进行竞争操作(此时A线程可能还木有释放锁) if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; }
    //如果失败或者前节点不是head节点,根据实际情况,考虑一下暂停当前线程 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }

    背景:A线程竞争锁成功,B线程竞争锁失败,AQS队列为空,此时B线程准备加入队列

    1,首先添加一个空节点,head,tail 链接到此节点

     2,B线程节点,加入AQS队列,A线程释放锁的时候,直接唤醒B线程竞争锁,竞争成功,队列前进一位即可

    A线程释放锁

         1,做减法--->AQS属性state一直被减到0(考虑可重入锁),代表这个A线程彻底释放锁

         2,在双向队列中,查找下一个节点,唤醒这个节点的线程

                 如果A线程直接抢到锁啦说明A线程所在的节点并木有添加到双向队列中,那么下一个节点就是双向队列的头节点

                 如果A线程所在的节点加入了双向队列,那么head节点为A线程所在的节点,下一个节点为head节点的下一个节点

     public void unlock() {
            sync.release(1);
        }
    //做减法--->AQS属性state一直被减到0(考虑可重入锁),代表这个A线程彻底释放锁
    protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */
    //在双向队列中,查找下一个节点,唤醒这个节点的线程
    Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); }
  • 相关阅读:
    开课吧-孤尽公开课视频内容整理笔记
    业务系统数据库设计经验总结(七)-强制使用选定索引
    业务系统数据库设计经验总结(六)-MySQL中ORDER BY LIMIT分页数据性能问题
    业务系统数据库设计经验总结(五)-MySQL中ORDER BY LIMIT分页数据重复问题
    mongoTemplate聚合统计字段类型为字符串的数据,根据时间日期月份进行统计,日期也为字符串
    预览在线的pdf
    react页面缓存 使用本地存储
    react页面缓存插件 react-router-cache-router
    README.md的基本语法使用
    eclipse tomcat的一些错误
  • 原文地址:https://www.cnblogs.com/huxuhong/p/13226930.html
Copyright © 2020-2023  润新知