Java多线程(二)——ReentrantLock源码解析(补充3——tryLock(long,TimeUnit) 锁超时)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

ReentrantLock源码解析(补充3——tryLock(long,TimeUnit)锁超时)

上一章仅介绍了 ReentrantLock 的常用方法以及公平锁、非公平锁的实现。这里对上一章做一些补充。主要是:

  • AQS 中阻塞的线程被唤醒后的执行流程
  • 可打断的锁 lock.lockInterruptibly()
  • 锁超时 lock.tryLock(long,TimeUnit) (本篇讲述)
  • 条件变量 Condition

1. 锁超时的应用

lock.tryLock(long,TimeUnit) 和 lock.tryLock() 是不同的。

  • lock.tryLock() 是非公平锁实现仅尝试获取锁一次
  • lock.tryLock(long, TimeUnit) 调用链分为公平与非公平锁
    • 在限定时间内尝试获取锁如果时间结束仍然获取不到将返回false
    • 限定时间内可以被中断等待。

使用方法:

public class ReentrantLockDemo1 {
    ReentrantLock lock = new ReentrantLock();

    Runnable r = new Runnable() {
        @Override
        public void run() {
            try {
                //尝试在2s内获取到锁
                if(!lock.tryLock(2,TimeUnit.SECONDS)){
                    System.out.println(Thread.currentThread().getName()+" 2s内获取不到锁");
                    return;
                }else{
                    System.out.println(Thread.currentThread().getName()+" 2s内获取到了锁");
                }
            } catch (InterruptedException e) {
                //如果在限定时间内被中断则响应中断并放弃等待锁资源
                e.printStackTrace();
                System.out.println(Thread.currentThread().getName()+" 2s内还未获取到锁,且被中断退出等待");
                return;
            }
            try{
                //临界区
                System.out.println(Thread.currentThread().getName()+" 获取到了锁进入临界区");
            }finally {
                //释放锁
                lock.unlock();
            }
        }
    };


    public static void main(String[] args)  {
        ReentrantLockDemo1 demo = new ReentrantLockDemo1();
        Thread t1= new Thread(demo.r,"t1");
        //主线程先获取到锁
        demo.lock.lock();
        System.out.println(Thread.currentThread().getName()+" 获取到锁");
        t1.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //主线程等待一段时间后才释放锁
        System.out.println(Thread.currentThread().getName()+" 释放了锁");
        demo.lock.unlock();
    }

}

上述代码运行结果:

main 获取到锁
t1 2s内获取不到锁
main 释放了锁
    
Process finished with exit code 0

由于主线程持有锁 5 秒而线程 t1 获取锁的限定时间为 2 秒限定时间后没有获取到锁放弃锁资源的等待。

如果让主线程中等待时间缩减为 Thread.sleep(50)运行结果如下:

main 获取到锁
main 释放了锁
t1 2s内获取到了锁
t1 获取到了锁进入临界区

Process finished with exit code 0

2. tryLock(long, TimeUnit) 的实现原理

通过调用 lock.tryLock( long , TimeUnit ) 进入到了 AQS 的 tryAcquireNanos(int arg, long) 方法:

//ReentrantLock
public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
//AbstractQueuedSynchronizer
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

tryAcquireNanos(int , long) 是一个模板方法且由于 tryAcquire() 方法需要子类实现所以分为了公平与非公平锁两种情况。

  • 公平锁:tryAcquire() 如果锁空闲将先查看 AQS 等待队列中是否等待节点如果没有才会CAS一次来尝试获取锁资源。如果锁非空闲可重入。
  • 非公平锁:tryAcquire() 如果锁空闲直接CAS一次来尝试获取锁资源。如果锁非空闲可重入。

在 doAcquireNanos() 方法中核心通过 LockSupport.parkNanos() 来定时唤醒并查看是否限时结束。

private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
    //如果等待时间不合法直接退出
    if (nanosTimeout <= 0L)
        return false;
    //计算截止时间
    final long deadline = System.nanoTime() + nanosTimeout;
    //封装到node结构中并加到AQS的等待队列队尾
    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;
            }
            //如果获取不到锁资源计算剩余时间
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                //剩余时间没了则退出等待
                return false;
            //如果还有剩余时间
            if (shouldParkAfterFailedAcquire(p, node) &&
                //如果剩余时间 > 自旋时间阈值
                nanosTimeout > spinForTimeoutThreshold)
                //将线程阻塞挂起直到剩余时间结束
                //1. 中途可以被interrupt()中断并进入下方的抛出异常响应中断
                //2. 唤醒后再次进入 for(;;) 循环最后进行一次锁资源的获取
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

一般情况下进入 doAcquireNanos() 将会先时获取一次锁如果没获取到则:

  • 如果剩余时间 < 自旋尝试获取锁时间阈值:不进行阻塞而是再次进入 for( ; ; )循环形成自旋尝试获取锁的动作。
  • 如果剩余时间 > 自旋尝试获取锁时间阈值:剩余多少时间就阻塞多少时间唤醒后再次进入 for( ; ; ) 循环最后一次尝试锁的获取。

不论是自旋获取锁还是阻塞等待都可以检测到线程 interrupt() 的情况如果中断标记为 true 可以响应中断即抛出 InterruptedException 异常。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java

“Java多线程(二)——ReentrantLock源码解析(补充3——tryLock(long,TimeUnit) 锁超时)” 的相关文章