Java小技巧:ReentrantLock的lock()和lockInterruptibly()的区别

开宗明义:这两个方法的核心区别在于当线程在等待获取锁时,是否响应中断(Interrupt)

Java小技巧:ReentrantLock的lock()和lockInterruptibly()的区别

1.lock()方法

  • 特性不可中断
  • 行为:当一个线程调用 lock() 方法时,如果锁已经被其他线程持有,它会一直阻塞(block),直到它成功获取到锁。在这个阻塞过程中,即使其他线程调用了它的 interrupt() 方法,它也不会立即返回或抛出异常,而是继续等待锁。中断的状态会被记录,当它最终获取到锁后,才会感受到这个中断(例如,在后续的 sleep() 或 wait() 调用中抛出 InterruptedException)。

2.lockInterruptibly()方法

  • 特性可中断
  • 行为:当一个线程调用 lockInterruptibly() 方法时,如果锁已经被其他线程持有,它会阻塞等待。但是,与 lock() 不同,如果在阻塞期间有其他线程调用了它的 interrupt() 方法,它会立即抛出 InterruptedException 异常,并终止等待锁的过程。这允许线程在等待锁的过程中被 “唤醒” 并处理中断逻辑。

代码示例

假设我们有一个场景:线程 A 先获取了锁,然后休眠 5 秒。线程 B 在这期间尝试获取同一个锁。

使用lock()(不可中断)

import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            lock.lock(); // 线程A获取锁
            try {
                System.out.println("线程A: 获取了锁,开始休眠5秒...");
                Thread.sleep(5000); // 持有锁休眠
                System.out.println("线程A: 休眠结束,释放锁。");
            } catch (InterruptedException e) {
                System.out.println("线程A: 在休眠时被中断了。");
                Thread.currentThread().interrupt(); // 恢复中断状态
            } finally {
                lock.unlock(); // 确保锁被释放
            }
        });

        Thread threadB = new Thread(() -> {
            System.out.println("线程B: 尝试获取锁...");
            lock.lock(); // 线程B会在这里阻塞,即使被中断也不会退出
            try {
                System.out.println("线程B: 成功获取了锁!");
            } finally {
                lock.unlock();
            }
        });

        threadA.start();
        Thread.sleep(1000); // 确保线程A先获取到锁
        threadB.start();

        Thread.sleep(2000); // 等待2秒
        System.out.println("主线程: 尝试中断线程B...");
        threadB.interrupt(); // 中断线程B

        threadA.join();
        threadB.join();
    }
}

运行结果分析:

  • 线程 A 获取锁并休眠。
  • 线程 B 调用 lock(),开始阻塞。
  • 主线程在 2 秒后中断线程 B。
  • 线程 B不会由于中断而停止等待,它会继续阻塞,直到线程 A 在 5 秒后释放锁。
  • 线程 B 最终会获取到锁,并打印 “线程 B: 成功获取了锁!”。中断请求被忽略了。

使用lockInterruptibly()(可中断)

import java.util.concurrent.locks.ReentrantLock;

public class LockInterruptiblyExample {
    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程A: 获取了锁,开始休眠5秒...");
                Thread.sleep(5000);
                System.out.println("线程A: 休眠结束,释放锁。");
            } catch (InterruptedException e) {
                System.out.println("线程A: 在休眠时被中断了。");
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        });

        Thread threadB = new Thread(() -> {
            System.out.println("线程B: 尝试获取锁 (可中断)...");
            try {
                lock.lockInterruptibly(); // 在这里等待,如果被中断会抛出异常
                try {
                    System.out.println("线程B: 成功获取了锁!");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                System.out.println("线程B: 在等待锁时被中断了!");
                Thread.currentThread().interrupt(); // 恢复中断状态
            }
        });

        threadA.start();
        Thread.sleep(1000);
        threadB.start();

        Thread.sleep(2000);
        System.out.println("主线程: 尝试中断线程B...");
        threadB.interrupt();

        threadA.join();
        threadB.join();
    }
}

运行结果分析:

  • 线程 A 获取锁并休眠。
  • 线程 B 调用 lockInterruptibly(),开始阻塞。
  • 主线程在 2 秒后中断线程 B。
  • 线程 B 在阻塞期间收到中断信号,立即抛出 InterruptedException。
  • 线程 B 的 catch 块捕获到异常,并打印 “线程 B: 在等待锁时被中断了!”。它没有获取到锁。

核心区别对比

特性

lock()

lockInterruptibly()

响应中断

不响应。线程会一直阻塞直到获取锁。

响应。线程在阻塞等待时如果收到中断信号,会立即抛出 InterruptedException 并退出。

异常抛出

不抛出 InterruptedException。

声明抛出 InterruptedException,调用者必须处理(捕获或抛出)。

适用场景

当你希望线程必须等到锁,不希望被外部因素打断时。

当你希望线程在等待锁的过程中能够响应中断,例如实现超时等待、或者需要能够撤销任务时。这是一种更灵活、更安全的设计。

性能

一般来说,lock() 的性能会略高于 lockInterruptibly(),由于它不需要处理中断检查的逻辑。

性能上可能会有微小的开销,由于需要在阻塞时检查中断状态。

总结

  • lock():是一个 “强硬” 的锁获取方式,一旦开始等待,就必须等到锁为止。
  • lockInterruptibly():是一个 “更合作” 的锁获取方式,它允许线程在等待期间被中断,从而可以优雅地退出等待,这在构建可撤销的任务或服务时超级有用。
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
陈顺民的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容