ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

JUC--009--locks5

2021-03-04 16:59:48  阅读:169  来源: 互联网

标签:JUC Thread -- lock 阻塞 获取 线程 tryLock 009


前面已经说了 ReentrantLock 使用 lock.lock() 方式获取锁,而且测试过这中获取锁的方式
如果获取不到锁就会阻塞,而且无法通过线程中断的方式解除阻塞。如果使用线程中断
试图解除阻塞,虽然不会成功,但是会把线程的状态改成已中断,这个可能会影响后续代码。
同时也说了公平锁和非公平锁的区别。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
获取锁的四种方式:
  lock.lock();                  阻塞式获取锁
  lock.lockInterruptibly();     阻塞式获取锁,可以被中断
  lock.tryLock()                非阻塞式获取锁,返回 true 表示获取了锁
  lock.tryLock(timeOut, unit)   带超时的阻塞式获取锁。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
现在看一下 lock.lockInterruptibly();  如何做到可以被中断的。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
示例代码:
开启两个子线程:线程1 进来先sleep(1000), 确保 线程2 能够获取锁。线程2 获取锁以后
准备长时间持有。 所以 线程1 阻塞在 lock.lockInterruptibly() 上。 但是主线程 5秒后
让 线程1 中断。线程1 中断后,从 阻塞中抛出异常。进入 finally 中执行。 这时一定
要判断一下,当前线程是否持有锁。已经看过释放锁的代码,没有锁的线程试图释放锁会
抛出异常。


private void t1() throws InterruptedException {
    ReentrantLock lock = new ReentrantLock();

    Runnable r = () ->
    {
        try {
            lock.lock();
            System.out.println("已获取锁:" + Thread.currentThread().getName());
            sleep(1000000);
        } finally {
            lock.unlock();
        }
    };

    Runnable rBlock = () ->
    {
        sleep(1000);
        try {
            lock.lockInterruptibly();
            System.out.println("--已获取锁:" + Thread.currentThread().getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if(lock.isHeldByCurrentThread()) {
                lock.unlock();
            } else {
                System.out.println("--未曾拥有:" + Thread.currentThread().isInterrupted());
            }
        }
    };

    Thread t1 = new Thread(rBlock, "[线程1]");
    Thread t2 = new Thread(r, "[线程2]");

    t1.start();
    t2.start();
    sleep(5000);
    t1.interrupt();
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lock.lockInterruptibly();

    //ReentrantLock::lockInterruptibly
    //调用的方法是不同的:lock.lock() 调用的是 acquire(1);
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    
    //AQS::acquireInterruptibly
    /*
     * 如果线程已经存在中断状态,下面进入第一个 if 直接抛出异常。
     * lock.lock()               -----1
     * lock.lockInterruptibly()  -----2
     * 假设我们想通过 1 获取锁,然后使用 2 重入一次。结果在 1 中阻塞。期间被中断
     * 只是 lock.lock 不能中断,所以没有什么表现,结果等到 1 获取了锁,然后
     * 使用 2 重入一次的时候,却有表现了,直接抛出异常。这个可能会出乎意料。
     * 
     * 前面已经多次提到过这个,这里最后一次重复了, 想要不抛出异常,清除线程中断状态。
     */
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //此时再尝试一次是不是能够获取锁
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
    
    
    //AQS::doAcquireInterruptibly
    //这个代码看着好眼熟啊, CountDownLatch::await 的调用代码和这个很像,
    //即这个方法: doAcquireSharedInterruptibly, 只他是共享的,这里的这个是独占的
    /*
     * 这个方法就不细说了,创建一个属于当前线程的节点,添加到双向链表,
     * 把自己前面的节点的 状态设置成 SIGNAL, 然后自己通过 unsafe.park 阻塞
     * 
     * 情况1: 自己线程并没有被中断,而是有人释放了锁,如果本线程Node就是 head 后面
     *        的那一个,结果被唤醒,去争抢锁,如果失败了在这里循环一次,继续阻塞。
     *        如果成功抢到锁,从 --OUT 处出去, finally 的代码条件不满足,不执行
     * 情况2:还在阻塞的时,线程被中断,从 --wake 处返回 true, 进入 if 语句,抛出异常
     *        执行 finally 中的代码(条件满足了), finally 中的代码就是 把自己的节点
     *        删除,如果自己还有后续节点,也唤醒自己的下一个节点。 然后返回到用户代码
     *        这里抛出的异常没有被捕获,直接抛给用户代码。
     *
     */
    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;       //----------OUT
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())    //----------wake
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

关于 lock.lockInterruptibly() 的代码执行流程就结束了。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
第三种获取锁的方式: lock.tryLock()
tryLock() 不会阻塞,而是立刻返回。根据返回值判断是否获取了锁。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
使用示例:

如果没有获取锁,那就去干点别的事情,然后循环的使用 tryLock() 尝试获取锁。
获取锁以后执行任务,最后还有释放锁

private void t1() throws InterruptedException {
    ReentrantLock lock = new ReentrantLock();

    Runnable r = () ->
    {
        try {
            lock.lock();
            System.out.println("已获取锁:" + Thread.currentThread().getName());
            sleep(5000);
        } finally {
            lock.unlock();
        }
    };

    Runnable rBlock = () ->
    {
        sleep(100); //确保线程2获取锁
        while(true) {
            if(lock.tryLock()) {
                System.out.println("已获取锁:" + Thread.currentThread().getName());
                try {
                    System.out.println("执行任务");
                    sleep(1000);
                    System.out.println("执行任务--完成,准备退出");
                    break;
                } finally {
                    System.out.println("释放锁");
                    lock.unlock();
                }
            } else {
                System.out.println("没有获取锁,干点别的");
                sleep(1000);
            }
        }
        System.out.println("线程退出");
    };

    Thread t1 = new Thread(rBlock, "[线程1]");
    Thread t2 = new Thread(r, "[线程2]");

    t1.start();
    t2.start();
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tryLock() 方法的 java doc 上介绍,这个方法时无视公平原则的。即使时公平锁,也有
很多的线程再排队等待获取锁,但是这个方法,如果锁可用,他就会去抢。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lock.tryLock()

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);   //难怪他会无视公平原则。
    }
    
    //Sync::nonfairTryAcquire
    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;
    }
    
没了,代码就这么多,使用这种方式获取锁, 要么获取到,要么获取不到,但自己不会
像前面的那些获取锁的方式,如果获取不到就创建一个Node节点, 添加到双向链表,并委
托自己的前置节点将自己唤醒。tryLock() 来去自由,感觉还洒脱。所以他也不存在中断,
排队等麻烦事。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lock.tryLock(timeOut, unit): 带超时的阻塞式获取锁方式。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~、、
使用代码:
tryLock(timeOut, unit) 带超时的获取锁。
获取锁立刻返回, 否则阻塞指定的时长,自动被唤醒,再次尝试获取锁。
阻塞时,如果自己被唤醒,也会参与抢锁行动,阻塞时也能被中断,所以会抛出中断异常。

使用代码意图:
先确保 线程2 获取锁,并持有锁10后释放。 线程1 开始带超时的阻塞 3 秒,但是 2 秒后,
主线程让 线程1 中断, 所以线程1抛出异常,然后异常被捕获,继续循环,再次带超时的
阻塞 3 秒。 此时线程2 仍然没有释放锁,超时时间到后,自动解除阻塞,返回 false,去
做了其他事情,然后再次循环, 再次尝试获取锁,又带超时的开始阻塞 ....
线程2 持有锁10秒后 释放了锁, 线程1立刻解除阻塞,开始执行代码。
执行完成后 break 退出,并释放锁。


private void t1() throws InterruptedException {
    ReentrantLock lock = new ReentrantLock();

    Runnable r = () ->
    {
        try {
            lock.lock();
            System.out.println("已获取锁:" + Thread.currentThread().getName());
            sleep(10000);
        } finally {
            lock.unlock();
        }
    };

    Runnable rBlock = () ->
    {
        sleep(100); //确保线程2获取锁
        while(true)
        {
            try {
                System.out.println("准备获取锁:" + Thread.currentThread().isInterrupted());
                if(lock.tryLock(3, TimeUnit.SECONDS))
                {
                    System.out.println("已获取锁:" + Thread.currentThread().getName());
                    try {
                        System.out.println("执行任务");
                        sleep(1000);
                        System.out.println("执行任务--完成,准备退出");
                        break;
                    } finally {
                        System.out.println("释放锁");
                        lock.unlock();
                    }
                } else {
                    System.out.println("没有获取锁,干点别的");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程退出");
    };

    Thread t1 = new Thread(rBlock, "[线程1]");
    Thread t2 = new Thread(r, "[线程2]");

    t1.start();
    t2.start();
    sleep(2000);
    t1.interrupt();
}

输出:
已获取锁:[线程2]
准备获取锁:false
java.lang.InterruptedException .....
准备获取锁:false
没有获取锁,干点别的
准备获取锁:false
没有获取锁,干点别的
准备获取锁:false
已获取锁:[线程1]
执行任务
执行任务--完成,准备退出
释放锁
线程退出
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tryLock(long timeout, TimeUnit unit) 
java doc 上介绍,如果是公平锁,那么他会遵守公平规则。 但是可以这样
if(lock.tryLock() || lock.tryLock(timeout, unit)) { ... } 先无视规则的
获取一次,只有获取不到锁,然后再来遵守规则。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lock.tryLock(3, TimeUnit.SECONDS)


    //Sync::tryLock
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    
    //AQS::tryAcquireNanos
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        //如果线程已经又中断状态,直接抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        /*
         * tryAcquire(arg) 的实现,公平锁和非公平锁是有区别的。
         * 所以 tryLock(timeout, unit) 非遵守公平锁的原则。
         *
         * tryLock(timeout, unit) 再次尝试获取锁,如果获取不到,就继续。
         */
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
    
    //AQS::doAcquireNanos
    //这个代码看着也熟悉,CountDownLatch.await(xx, xx) 调用的方法和
    //这个很像,那个是共享模式, 这里的是独占模式。
    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        /*
         * 如果 tryLock(小于等于0的数),  如果执行到这里之前能够获取锁,就获取了。
         * 如果不能获取锁,来到这里,自己不会被加到队列,不会被阻塞,而是立刻返回。
         * 
         * 这样的话,我们就能实现一个遵守公平规则的 tryLock(), 即
         * tryLock(0, unit) 。 按照公平规则获取锁,获取不到也不阻塞
         */
        if (nanosTimeout <= 0L)
            return false;
            
        //未来到期的相对时间
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                //自己不是被打断方式唤醒(即正常唤醒),或者自己还没有被阻塞过。
                //如果自己是 head 的后一个, 参与抢锁行动
                if (p == head && tryAcquire(arg)) {
                    //抢到锁, 把head干掉,自己变成新 head
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    
                    //获取锁,正常退出, finally 中的不用执行了。
                    //因为 head 被干掉,自己变成 head, 所以相当于把自己删除了,
                    //而且自己获取了锁,也没有必要唤醒下一个起来尝试抢一下锁。
                    return true;  
                }
                
                //离到期(超时)剩余的时间
                nanosTimeout = deadline - System.nanoTime();
                
                /*
                 * 这里的 if 内部是一个出口:
                 * 情况1:第一次进来,把自己的 node 刚添加进去,结果时间到到期了。 
                 *        此时自己没有获取锁,而且 finally 种的条件满足,会把自己
                 *        刚刚添加进来的 node 删除掉
                 * 情况2:像情况1一样,自己是第一次进来,虽然第一次循环这里不满足,
                 *        但是自己到期时间已经小于 spinForTimeoutThreshold(1000纳秒)
                 *        此时如果让线程阻塞不太划算, 1000纳秒太短,刚阻塞就要解开。
                 *        因此就不阻塞了, 在这个循环中转2圈,时间就耗掉了。所以
                 *        情况2就是没有阻塞,在这转圈耗时间,待满足退出,执行 finally
                 * 情况3:到期时间还有点长,阻塞了,然后超时后自动解除阻塞,再来一次
                 *        循环,这里就满足了,返回 false, 执行 finally
                 * 不管哪种情况,如果代码从这个出口出去的,就表示并没有获取锁,
                 * 而且执行 finally, 把自己的 node 从 双向链表中删除,如果有需要还要
                 * 唤醒自己的下一个节点。
                 */
                if (nanosTimeout <= 0L)
                    return false;
                
                //把自己的前置节点的状态设置成 SIGNAL
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //剩余时间还很长,大于这个常数 1000 纳秒
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout); //待超时阻塞
                
                //超时前,自己被中断,从阻塞中返回,抛出异常
                //执行 finally , 清除自己的 node, 如果有需要,还要唤醒下一个节点
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                //从双向链表中清除参数 node , 如果有需要同时唤醒 node 的下一个节点
                cancelAcquire(node);
        }
    }
    
根据上面的源码: 如果自己的线程被打断,那么抛出的异常没有被捕获,会抛给用户代码。
而且 在抛出异常前使用  Thread.interrupted() 获取线程是否被打断,这个方法会
清除线程的中断状态的, 所以用户代码会收到一个异常,但是获取中断状态是 false, 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
到这里四种获取锁的方式说完了0

    方式                |   阻塞       |  可否打断    |   遵守公平规则
------------------------+--------------+--------------+-----------------
lock.lock()             |   永久阻塞   |    不可      |    遵守
------------------------+--------------+--------------+-----------------
lock.lockInterruptibly()|   永久阻塞   |    可        |    遵守
------------------------+--------------+--------------+-----------------
lock.tryLock()          |   非阻塞     |    ---       |    不遵守
------------------------+--------------+--------------+-----------------
lock.tryLock(x, unit)   |   超时阻塞   |    可        |    遵守





 

标签:JUC,Thread,--,lock,阻塞,获取,线程,tryLock,009
来源: https://blog.csdn.net/szw727/article/details/114371795

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有