Java 显式锁超全教程:可中断、超时锁、Condition 用法一次学透

在 Java 中,显式锁(Explicit Lock) 是相对于 synchronized 隐式锁而言的,指通过代码手动控制锁的获取、释放过程的锁机制,核心接口是 java.util.concurrent.locks.Lock,最常用实现类是 ReentrantLock。显式锁提供了比 synchronized 更灵活的锁操作能力,同时保留了线程安全的核心特性。

一、显式锁的核心优势(对比 synchronized

特性显式锁(Lock隐式锁(synchronized
锁获取 / 释放手动调用 lock()/unlock(),必须显式释放自动获取(进入代码块),自动释放(退出 / 异常)
可中断锁支持(lockInterruptibly()不支持(阻塞时无法被中断)
超时获取锁支持(tryLock(long, TimeUnit)不支持(一直阻塞直到获取)
公平锁支持支持(构造函数指定 fair=true不支持(非公平锁,无法配置)
锁状态查询支持(isLocked()/hasQueuedThreads()不支持(无公开 API 查询)
条件变量(Condition)支持(多个 Condition 绑定一个 Lock)支持(仅一个,通过 wait()/notify()
性能(高并发)竞争激烈时性能更稳定低并发友好,高并发下性能可能波动

二、核心接口与实现类

1. 顶层接口:Lock

定义了显式锁的核心操作,必须实现的方法:

public interface Lock {
    // 阻塞获取锁(不可中断,直到获取成功)
    void lock();

    // 可中断获取锁(阻塞时可被其他线程中断,抛出 InterruptedException)
    void lockInterruptibly() throws InterruptedException;

    // 尝试获取锁(非阻塞):成功返回 true,失败返回 false(立即返回,不阻塞)
    boolean tryLock();

    // 超时尝试获取锁:在指定时间内获取成功返回 true,超时/中断返回 false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 释放锁(必须手动调用,否则会导致锁泄漏)
    void unlock();

    // 创建绑定到当前锁的条件变量(Condition)
    Condition newCondition();
}

2. 常用实现类:ReentrantLock

  • 可重入锁:同一线程可多次获取锁(与 synchronized 一致),解锁次数需与加锁次数一致,否则锁无法释放。
  • 公平 / 非公平锁
    • 非公平锁(默认):获取锁时不排队,允许 “插队”,性能更高(大多数场景首选)。
    • 公平锁:按线程请求顺序分配锁,避免饥饿,但性能开销更大(需维护等待队列)。
  • 构造函数:
    // 非公平锁(默认)
    ReentrantLock lock = new ReentrantLock();
    // 公平锁
    ReentrantLock fairLock = new ReentrantLock(true);
    

三、显式锁的使用规范

核心原则:必须在 finally 块中释放锁,避免因异常导致锁泄漏(线程持有锁但未释放,其他线程无法获取)。

1. 基础用法(不可中断锁)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ExplicitLockDemo {
    // 初始化非公平锁
    private static final Lock lock = new ReentrantLock();
    private static int count = 0;

    public static void increment() {
        // 1. 获取锁
        lock.lock();
        try {
            // 2. 临界区(线程安全操作)
            count++;
        } finally {
            // 3. 释放锁(必须在 finally 中,确保异常时也释放)
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 启动 1000 个线程测试
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < 1000; i++) {
            threads[i] = new Thread(ExplicitLockDemo::increment);
            threads[i].start();
        }
        // 等待所有线程结束
        for (Thread t : threads) {
            t.join();
        }
        System.out.println("最终计数:" + count); // 输出 1000(线程安全)
    }
}

2. 可中断锁(lockInterruptibly()

适用于 “获取锁时允许被中断” 的场景(如超时任务、用户取消操作):

public static void interruptibleLockDemo() throws InterruptedException {
    Lock lock = new ReentrantLock();
    Thread t1 = new Thread(() -> {
        try {
            // 尝试获取锁(可被中断)
            lock.lockInterruptibly();
            try {
                System.out.println("线程1获取锁成功,开始执行");
                Thread.sleep(5000); // 模拟长时间操作
            } finally {
                lock.unlock();
            }
        } catch (InterruptedException e) {
            System.out.println("线程1获取锁时被中断");
        }
    });

    Thread t2 = new Thread(() -> {
        try {
            lock.lockInterruptibly(); // 阻塞等待 t1 释放锁
            System.out.println("线程2获取锁成功");
        } catch (InterruptedException e) {
            System.out.println("线程2获取锁时被中断");
        }
    });

    t1.start();
    Thread.sleep(1000); // 确保 t1 先获取锁
    t2.start();
    Thread.sleep(1000); // 让 t2 阻塞在获取锁
    t2.interrupt(); // 中断 t2 的锁等待
}

3. 超时获取锁(tryLock(long, TimeUnit)

避免线程无限期阻塞,适用于 “超时放弃” 的场景:

public static void tryLockWithTimeout() throws InterruptedException {
    Lock lock = new ReentrantLock();
    Thread t = new Thread(() -> {
        boolean acquired = false;
        try {
            // 尝试在 2 秒内获取锁
            acquired = lock.tryLock(2, TimeUnit.SECONDS);
            if (acquired) {
                System.out.println("线程获取锁成功");
            } else {
                System.out.println("线程超时未获取到锁,放弃操作");
            }
        } catch (InterruptedException e) {
            System.out.println("线程被中断");
        } finally {
            // 只有获取锁成功后,才需要释放
            if (acquired) {
                lock.unlock();
            }
        }
    });

    lock.lock(); // 主线程先获取锁
    t.start();
    Thread.sleep(3000); // 主线程持有锁 3 秒(超过 t 的超时时间)
    lock.unlock();
}

4. 条件变量(Condition

Condition 是显式锁的 “等待 / 通知” 机制,相比 synchronized 的 wait()/notify() 更灵活(一个锁可绑定多个 Condition,实现更精细的线程通信)。

核心方法:

  • await():当前线程释放锁,进入条件等待队列(类似 wait())。
  • signal():唤醒条件等待队列中的一个线程(类似 notify())。
  • signalAll():唤醒条件等待队列中的所有线程(类似 notifyAll())。

示例:生产者 - 消费者模型(多个生产者 / 消费者,基于队列)

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionDemo {
    private static final Lock lock = new ReentrantLock();
    // 条件1:队列不为空(消费者等待此条件)
    private static final Condition notEmpty = lock.newCondition();
    // 条件2:队列不为满(生产者等待此条件)
    private static final Condition notFull = lock.newCondition();
    private static final Queue<Integer> queue = new LinkedList<>();
    private static final int MAX_SIZE = 5;

    // 生产者:向队列添加元素
    public static void produce(int num) throws InterruptedException {
        lock.lock();
        try {
            // 队列满时,生产者等待(释放锁)
            while (queue.size() == MAX_SIZE) {
                System.out.println("队列满,生产者等待");
                notFull.await(); // 等待“队列不为满”条件
            }
            queue.offer(num);
            System.out.println("生产者生产:" + num + ",队列大小:" + queue.size());
            notEmpty.signal(); // 唤醒等待“队列不为空”的消费者
        } finally {
            lock.unlock();
        }
    }

    // 消费者:从队列获取元素
    public static Integer consume() throws InterruptedException {
        lock.lock();
        try {
            // 队列空时,消费者等待(释放锁)
            while (queue.isEmpty()) {
                System.out.println("队列空,消费者等待");
                notEmpty.await(); // 等待“队列不为空”条件
            }
            Integer num = queue.poll();
            System.out.println("消费者消费:" + num + ",队列大小:" + queue.size());
            notFull.signal(); // 唤醒等待“队列不为满”的生产者
            return num;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        // 启动 2 个生产者
        new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    produce(i);
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                for (int i = 11; i <= 20; i++) {
                    produce(i);
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // 启动 3 个消费者
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    while (true) {
                        consume();
                        Thread.sleep(800);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

四、显式锁的注意事项

  1. 必须释放锁lock() 后必须在 finally 中调用 unlock(),否则线程异常时会导致锁泄漏(其他线程永远无法获取锁)。
  2. 可重入性:同一线程多次 lock() 需对应多次 unlock(),否则锁无法完全释放(例如:加锁 2 次,解锁 1 次,其他线程仍无法获取)。
  3. 公平锁的性能开销:公平锁需维护等待队列的顺序,高并发下性能比非公平锁低,非特殊需求(如避免线程饥饿)不建议使用。
  4. Condition 的等待条件await() 前必须先获取锁,且需在 while 循环中判断条件(而非 if),避免 “虚假唤醒”(线程被唤醒后条件可能已不满足)。
  5. 与 synchronized 的选择
    • 简单场景(如单临界区、无特殊需求):优先用 synchronized(语法简洁,JVM 优化更成熟)。
    • 复杂场景(如可中断、超时、公平锁、多条件变量):用 ReentrantLock

五、其他显式锁实现类

除了 ReentrantLock,Java 还提供了其他常用显式锁:

  1. ReentrantReadWriteLock:读写锁(分离读锁和写锁),支持 “多读单写”,读操作并发执行,写操作互斥,适用于 “读多写少” 场景(如缓存、配置读取)。
  2. StampedLock:Java 8 新增的乐观读写锁,性能优于 ReentrantReadWriteLock,支持乐观读、悲观读、写锁三种模式。

总结

显式锁(Lock 接口)是 Java 并发编程中灵活、强大的锁机制,核心优势是支持可中断、超时获取、公平锁、多条件变量等特性。使用时需严格遵循 “获取锁后在 finally 中释放” 的规范,避免锁泄漏。在简单场景下 synchronized 更简洁高效,复杂场景下 ReentrantLock 更具优势。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

canjun_wen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值