ReetrantLock源码分析

ReentrantLock类的大部分逻辑,都是其均继承自AQS的内部类Sync实现的

啥是AQS:

Java并发编程核心在于java.concurrent.util包而juc当中的大多数同步器实现都是围绕着共同的基础行为,比如**「等待队列、条件队列、独占获取、共享获取」等,而这个行为的抽象就是基于AbstractQueuedSynchronizer简称AQS 它定义了一套多线程访问共享资源的同步器框架,是一个「依赖状态(state)的同步器」**。

以公平锁为例子:

1    public static void main(String[] args) { 2        ReentrantLock lock = new ReentrantLock(true); 3        lock.lock(); //加锁 断点处 4        try { 5            Thread.sleep(5000); 6        }catch (InterruptedException e) { 7            e.printStackTrace(); 8        }finally { 9          lock.unlock(); 10        } 11    

公平锁、非公平锁

1public ReentrantLock(boolean fair) { //ReetrantLock的有参构造函数 2    sync = fair ? new FairSync() : new NonfairSync(); 3}

在加锁行打断点运行, 我们可以看到参数:

image-20201105163128535

image-20201105163128535

记住这几个值,后面会用到.

单步步入 F7

image-20201105163427610

image-20201105163427610

看到lock()调用了Sync实例中的lock()方法

我们可以看到Sync是在ReentrantLock类中的一个抽象内部类 继承于AbstractQueuedSynchronizer (AQS)(抽象队列同步器) image-20201105163720831

点击Sync类中lock抽象接口方法 我们可以发现ReetrantLock有公平和非公平锁两种方式实现方式.由于本章我们使用公平锁讲解所以我们选择公平锁的实现方式继续向下调试代码 image-20201105165340504

1 /** 2     * Sync object for fair locks 3     */ 4    static final class FairSync extends Sync { //公平锁实现方式 继承于Sync 5        private static final long serialVersionUID = -3000897897090466540L; 6 7        final void lock() { 8            acquire(1);//获取锁 传入参数1 9        } 10..

向下⬇️

1  public final void acquire(int arg) {  //acquire方法接收参数 1 2        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 3           //这里有两个操作 使用短路与 如果第一个操作成功第二个操作不执行 4      //1.尝试以独占模式获取锁,在成功时返回  5      //2.线程进入队列排队 6            selfInterrupt(); 7    }

方式1 首先尝试获取锁

1  /** 2         * Fair version of tryAcquire.  Don't grant access unless 3         * recursive call or no waiters or is first. 4         */ 5        protected final boolean tryAcquire(int acquires) {  //参数:1 6            final Thread current = Thread.currentThread();  //获取当前线程 7            int c = getState(); //还记得我们第一个断点截图的state值是0 8            if (== 0) {   //是0 说明我们是第一次获取锁,如果不是0说明是重入,会进入else 9                if (!hasQueuedPredecessors() && 10         //公平锁和非公平锁,主要是在方法 tryAcquire 中,是否有 !hasQueuedPredecessors() 判断。 11                    compareAndSetState(0, acquires)) {//通过cas操作state更新,期望值是0,新值是1 12                    setExclusiveOwnerThread(current);//设置当前拥有独占访问的线程。 13                    return true; 14                } 15            } 16            else if (current == getExclusiveOwnerThread()) { //如果 是重入情况 会进入这里. 17          //进入前会判断是否是当前线程拿的锁 18                int nextc = c + acquires; //假设重入次数为2  那么nextc = 2+1 =3 19                if (nextc < 0) 20                    throw new Error("Maximum lock count exceeded"); 21                setState(nextc); //更新state值 22                return true; 23            } 24            return false; 25        }

先判断state是否为0,「如果为0就执行上面提到的lock方法的前半部分」,通过CAS操作将state的值从0变为1,否则判断当前线程是否为exclusiveOwnerThread,然后把state++,也就是重入锁的体现,「我们注意前半部分是通过CAS来保证同步,后半部分并没有同步的体现」,原因是:后半部分是线程重入,再次获得锁时才触发的操作,此时当前线程拥有锁,所以对ReentrantLock的属性操作是无需加锁的。「如果tryAcquire()获取失败,则要执行addWaiter()向等待队列中添加一个独占模式的节点。」

1public final boolean hasQueuedPredecessors() { 2    Node t = tail; // 根据初始化顺序倒序获取字段 3    Node h = head; 4    Node s; 5    return h != t && 6        ((= h.next) == null || s.thread != Thread.currentThread()); 7} 8//在这个判断中主要就是看当前线程是不是同步队列的首位,是:true、否:false 9//这部分涉及公平锁的实现,CLH(Craig,Landin andHagersten)。三个作者的首字母组合

啥是CLH

CLH锁即Craig, Landin, and Hagersten (CLH) locks。CLH锁是一个自旋锁。能确保无饥饿性。提供先来先服务的公平性。 为什么说JUC中的实现是基于CLH的“变种”,因为原始CLH队列,一般用于实现自旋锁。而JUC中的实现,获取不到锁的线程,一般会时而阻塞,时而唤醒。

image-20201106144400019 1.获取不到锁的线程,会进入队尾,然后自旋,直到其前驱线程释放锁,具体位置是放在tail后的null位置,并让新对象的next指向Null 2.如果head成功拿到了锁 此时 把head中包含的线程指向为Null并将head的前任设置为head后清除现任head

方式2 没获取到锁,需要进入队列排队:

进入方式需要改造项目让多个线程进行抢占资源改造如下:

1import java.util.concurrent.locks.ReentrantLock; 2 3class Scratch { 4    public static void main(String[] args) { 5        ReentrantLock lock = new ReentrantLock(true); 6        for (int i = 0; i < 5; i++) { 7            new Thread(()->{ 8                lock.lock(); 9                try { 10                    Thread.sleep(100); 11                    System.out.println(Thread.currentThread().getName()+"工作结束...."); 12                }catch (InterruptedException e) { 13                    e.printStackTrace(); 14                }finally { 15                    lock.unlock(); 16                } 17            }).start(); 18        } 19    } 20}

回到这里发现等待队列addWaiter方法添加了一个Null 将自己加入CLH队列的尾部 关注公众号:[JAVA宝典]

image-20201105171529838

image-20201105171529838

发现等待队列addWaiter方法添加了一个Null null的含义是: image-20201105171624412

使用null是私有占用资源模式,使用new Node()是 共享模式.(顺带一提,writelock不互斥,就是使用的共享模式)

Node还有几个等待状态:

1 /** 2         * Status field, taking on only the values: 3         *   SIGNAL:     The successor of this node is (or will soon be) 4         *               blocked (via park), so the current node must 5         *               unpark its successor when it releases or 6         *               cancels. To avoid races, acquire methods must 7         *               first indicate they need a signal, 8         *               then retry the atomic acquire, and then, 9         *               on failure, block. 10         *   CANCELLED:  This node is cancelled due to timeout or interrupt. 11         *               Nodes never leave this state. In particular, 12         *               a thread with cancelled node never again blocks. 13         *   CONDITION:  This node is currently on a condition queue. 14         *               It will not be used as a sync queue node 15         *               until transferred, at which time the status 16         *               will be set to 0. (Use of this value here has 17         *               nothing to do with the other uses of the 18         *               field, but simplifies mechanics.) 19         *   PROPAGATE:  A releaseShared should be propagated to other 20         *               nodes. This is set (for head node only) in 21         *               doReleaseShared to ensure propagation 22         *               continues, even if other operations have 23         *               since intervened. 24         *   0:          None of the above 25         * 26         * The values are arranged numerically to simplify use. 27         * Non-negative values mean that a node doesn't need to 28         * signal. So, most code doesn't need to check for particular 29         * values, just for sign. 30         * 31         * The field is initialized to 0 for normal sync nodes, and 32         * CONDITION for condition nodes.  It is modified using CAS 33         * (or when possible, unconditional volatile writes). 34         */ 35        volatile int waitStatus;

在addWaiter(Node.EXCLUSIVE)处断点:

1 /** 2     * 根据给定的模式为当前节点创建一个Node 3     * 4     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared 5   // 6     * @return the new node 7     */ 8    private Node addWaiter(Node mode) { 9        //线程对应的Node 10        Node node = new Node(Thread.currentThread(), mode); 11        // Try the fast path of enq; backup to full enq on failure 12        Node pred = tail; 13        //尾节点不为空 14        if (pred != null) { 15            //当前node的前驱指向尾节点 16            node.prev = pred; 17            //将当前node设置为新的尾节点 18            //如果cas操作失败,说明线程竞争 19            if (compareAndSetTail(pred, node)) { 20                pred.next = node; 21                return node; 22            } 23        } 24        //lockfree的方式插入队尾 25        enq(node);  //只有在 tail == null时才进入 26        return node; 27    }

先找到等待队列的tail节点pred,如果pred!=null,就把当前线程添加到pred后面进入等待队列,如果不存在tail节点执行enq()

1    private Node enq(final Node node) { 2        //经典的lockfree算法:循环+CAS 3        for (;;) { 4            Node t = tail; 5            //尾节点为空 6            if (== null) { // Must initialize 7                //初始化头节点 8                if (compareAndSetHead(new Node())) 9                    tail = head; 10            } else { 11                node.prev = t; 12                if (compareAndSetTail(t, node)) { 13                    t.next = node; 14                    return t; 15                } 16            } 17        } 18    }

这里进行了循环,「如果此时存在了tail就执行同上一步骤的添加队尾操作,如果依然不存在,就把当前线程作为head结点。」 插入节点后,调用acquireQueued()进行阻塞

1    /** 2     * Acquires in exclusive uninterruptible mode for thread already in 3     * queue. Used by condition wait methods as well as acquire. 4     * 5     * @param node the node 6     * @param arg the acquire argument 7     * @return {@code true} if interrupted while waiting 8     */ 9    final boolean acquireQueued(final Node node, int arg) { 10        boolean failed = true; 11        try { 12            boolean interrupted = false; 13            for (;;) { 14                final Node p = node.predecessor(); 15                if (== head && tryAcquire(arg)) { 16                    setHead(node); 17                    p.next = null; // help GC 18                    failed = false; 19                    return interrupted; 20                } 21                if (shouldParkAfterFailedAcquire(p, node) && 22                    parkAndCheckInterrupt()) 23                    interrupted = true; 24            } 25        } finally { 26            if (failed) 27                cancelAcquire(node); 28        } 29    }

先获取当前节点的前一节点p,如果p是head的话就再进行一次tryAcquire(arg)操作,如果成功就返回,否则就执行**「shouldParkAfterFailedAcquire、parkAndCheckInterrupt来达到阻塞效果;」**

unlock

在unlock处打断点

进入了

1    public void unlock() { 2        sync.release(1); 3    } 4

image-20201106142501425

image-20201106142501425

继续跟进发现进入release方法,继续查看tryRelease(arg)方法

尝试释放锁有三种实现 我们点ReetrantLock实现方式 image-20201106142623929

源码: image-20201106142942146

1        protected final boolean tryRelease(int releases) { //最后进入到实际解锁源码中 2            int c = getState() - releases; 3            if (Thread.currentThread() != getExclusiveOwnerThread()) //判断持有线程和当前执行线程是否是同一个,否则报错 4                throw new IllegalMonitorStateException(); 5            boolean free = false; 6            if (== 0) { //判断释放掉releases参数后是否是0 如果 为0 设置当前排他锁的占有线程为Null 7                free = true; 8                setExclusiveOwnerThread(null); 9            } 10            setState(c);//更新state为0 11            return free;//返回解锁是否成功 12        }
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Java日期时间API系列31

  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前