zoukankan      html  css  js  c++  java
  • ReentrantLock中lock/trylock/lockInterruptibly方法的区别及源码解析

    看了几篇关于这三者区别的文章,但都说的不够具体,自己去读了下源码,大概是清楚了三者的功能,不想了解源码的可以跳到最后看总结。

    首先,ReentrantLock类中使用了大量的CAS操作,也就是CompareAndSwap原子操作,依靠硬件保证互斥与同步,然后说下interrupt()方法。每个线程都有一个interrupt标志。当线程在阻塞状态时,如sleep、wait、await(park)、join,此时如果对该进程调用interrupt()方法,该线程会被唤醒、interrupt标志被修改,并抛出InterruptedException异常(一旦捕捉到异常,立刻重置interrupt标志),因此上述的那些方法被要求为必须捕捉异常;当线程在运行中并未进入任何阻塞状态时,则interrupt()操作不会打算线程执行,只会修改该线程interrupt标志,此时可以通过Interrupted()/isInterrupted()查看并作出处理(前者会重置该标志位),通常使用while循环来检查。

    一、 lock()方法

    使用lock()获取锁,若获取成功,标记下是该线程获取到了锁用于锁重入),然后返回。若获取失败,这时跑一个for循环,循环中先将线程阻塞放入等待队列,当被调用signal()时线程被唤醒,这时进行锁竞争(因为默认使用的是非公平锁),如果此时用CAS获取到了锁那么就返回,如果没获取到那么再次放入等待队列,等待唤醒,如此循环。其间就算外部调用了interrupt(),循环也会继续走下去。一直到当前线程获取到了这个锁,此时才处理interrupt标志,若有,则执行 Thread.currentThread().interrupt(),结果如何取决于外层的处理。lock()最终执行的方法如下:

        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) { //如果竞争得到了锁
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return interrupted; //获取成功返回interrupted标志
                   }
                     // 只修改标志位,不做其他处理
                     if (shouldParkAfterFailedAcquire(p, node) && <strong>parkAndCheckInterrupt()</strong>) 
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }

    其中parkAndCheckInterrupt()调用了LockSupport.park(),该方法使用Unsafe类将进程阻塞并放入等待队列,等待唤醒,和await()有点类似。

    可以看到循环中检测到了interrupt标记,但是仅做 interrupted = true 操作,直到获取到了锁,才return interrupted,然后处理如下

       public final void acquire(int arg) {
            if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt(); // 执行Thread.currentThread().interrupt()
        }

    二、 lockInterruptibly()方法

    和lock()相比,lockInterruptibly()只有略微的修改,for循环过程中,如果检测到interrupt标志为true,则立刻抛出InterruptedException异常,这时程序变通过异常直接返回到最外层了,又外层继续处理,因此使用lockInterruptibly()时必须捕捉异常。lockInterruptibly()最终执行的方法如下:

        private void doAcquireInterruptibly(int arg)
            throws InterruptedException {
            final Node node = addWaiter(Node.EXCLUSIVE);
            boolean failed = true;
            try {
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return; //获取成功返回
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        throw new InterruptedException(); //直接抛出异常
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }

    三、 tryLock()方法

    使用tryLock()尝试获取锁,若获取成功,标记下是该线程获取到了锁,然后返回true;若获取失败,此时直接返回false,告诉外层没有获取到锁,之后的操作取决于外层,代码如下:

            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;
            }
    else if 中的情况属于锁重入情况,即外层函数已经获取到这个锁了,内部的函数又再次对其进行获取,那么对计数器加一,不再循环加锁。

    还有一种情况,即使用tryLock(long timeout, TimeUnit unit)获取锁,此时有点像lock()和lockInterruptibly()的混合体,若获锁取成功,直接返回true;若获取失败跑一个for循环,则用 LockSupport.parkNanos(this, nanosTimeout) 阻塞进程并放入等待队列,等待唤醒,同时timeout扣减每次循环消耗的时间,当timeout用尽时如果依然没有获取到锁,那么就返回false。对于循环期间收到的interrupt()的处理,这儿和lockInterruptibly()一样,一旦检测到,那么直接抛出异常。代码如下:

        private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
            long lastTime = System.nanoTime();
            final Node node = addWaiter(Node.EXCLUSIVE);
            boolean failed = true;
            try {
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return true; //获取成功返回
                    }
                    if (nanosTimeout <= 0)
                        return false;
                    if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
                        <strong>LockSupport.parkNanos(this, nanosTimeout)</strong>; //带timeout的阻塞
                    long now = System.nanoTime();
                    nanosTimeout -= now - lastTime;
                    lastTime = now; // 更新timeout
                    if (Thread.interrupted())
                        throw new InterruptedException(); //直接抛出异常
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }

    总结

    1. lock()  阻塞式地获取锁,只有在获取到锁后才处理interrupt信息
    2. lockInterruptibly() 阻塞式地获取锁,立即处理interrupt信息,并抛出异常
    3. tryLock()  尝试获取锁,不管成功失败,都立即返回true、false
    4. tryLock(long timeout, TimeUnit unit)在timeout时间内阻塞式地获取锁,成功返回true,超时返回false,同时立即处理interrupt信息,并抛出异常

    以上就是我对这三种方法的认识,第一次写技术博客,发现还是挺耗时间的,如果有什么错误还望大家请指正。


  • 相关阅读:
    MySQL Explain学习笔记
    postman测试文件上传接口教程
    URLDecoder异常Illegal hex characters in escape (%)
    第三章 jQuery总结 参考文本
    史上最简单的一道面试题!坑人吧
    cookie和session的关联关系
    服务器配置
    Rancher 添加主机无法显示、添加主机无效的解决办法
    .NET Core 跨平台 串口通讯 ,Windows/Linux 串口通讯,flyfire.CustomSerialPort 的使用
    .Net Core 跨平台应用使用串口、串口通信 ,可能出现的问题、更简洁的实现方法
  • 原文地址:https://www.cnblogs.com/yangcheng33/p/6557325.html
Copyright © 2011-2022 走看看