轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

      • Java并发-线程基础与synchronized关键字
      • Java并发-重入锁ReentrantLock详解与实践
        • 一、什么是ReentrantLock
        • 二、基础使用方式
        • 三、中断响应能力
        • 四、超时机制
        • 五、公平锁与非公平锁
        • 六、Condition:锁的得力助手
        • 七、实际应用:ArrayBlockingQueue
        • 八、总结
      • Java并发-信号量Semaphore
      • Java并发-读写锁ReadWriteLock
      • Java并发-倒计时器CountDownLatch
      • Java并发-栅栏CyclicBarrier
      • Java并发-LockSupport线程阻塞工具类
      • Java并发-线程池ThreadPoolExecutor
      • Java并发-阻塞队列BlockingQueue
      • Java并发-以空间换时间之ThreadLocal
      • Java并发-无锁策略CAS与atomic包
      • Java并发-JDK并发容器
      • Java并发-异步调用结果之Future和CompletableFuture
      • Java并发-Fork Join框架
      • Java并发-调试与诊断
    • 经验

    • JVM

    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 并发
轩辕李
2018-06-02
目录

Java并发-重入锁ReentrantLock详解与实践

在Java并发编程中,除了使用synchronized关键字外,JDK还提供了更加灵活强大的锁机制。ReentrantLock(重入锁)作为显式锁的典型代表,不仅能够实现与synchronized相同的互斥和内存可见性保证,还提供了更丰富的功能特性。

# 一、什么是ReentrantLock

ReentrantLock是Java并发包(java.util.concurrent.locks)中提供的可重入互斥锁。之所以叫"重入锁",是因为同一个线程可以多次获取同一把锁而不会发生死锁。相比synchronized,它提供了更细粒度的锁控制和更丰富的功能。

# 二、基础使用方式

让我们从一个简单的例子开始了解ReentrantLock的基本用法:

public class ReentrantLockDemo implements Runnable {
    private static final ReentrantLock lock = new ReentrantLock();
    private static int counter = 0;

    @Override
    public void run() {
        for (int i = 0; i < 1000000; i++) {
            lock.lock();
            try {
                counter++;
            } finally {
                lock.unlock();
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockDemo demo = new ReentrantLockDemo();
        Thread t1 = new Thread(demo);
        Thread t2 = new Thread(demo);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("最终结果:" + counter);
    }
}

这个例子展示了ReentrantLock的核心用法模式:通过lock()方法获取锁,在finally块中通过unlock()方法释放锁。这种显式的加锁和解锁方式给了我们更多的控制权。

性能对比:在早期的Java版本中(Java 5),ReentrantLock的性能明显优于synchronized。但从Java 6开始,synchronized经过大量优化后,两者的性能已经非常接近。

# 三、中断响应能力

synchronized关键字的一个局限性在于,当线程在等待锁时无法响应中断。而ReentrantLock提供了lockInterruptibly()方法来解决这个问题:

public class InterruptibleLockDemo implements Runnable {
    private static final ReentrantLock lock1 = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();
    private final int lockType;

    public InterruptibleLockDemo(int lockType) {
        this.lockType = lockType;
    }

    @Override
    public void run() {
        try {
            if (lockType == 1) {
                // 线程1:先获取lock1,再获取lock2
                lock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                    lock2.lockInterruptibly();
                    try {
                        System.out.println(Thread.currentThread().getName() + " 获取到两把锁");
                    } finally {
                        lock2.unlock();
                    }
                } finally {
                    lock1.unlock();
                }
            } else {
                // 线程2:先获取lock2,再获取lock1
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                    lock1.lockInterruptibly();
                    try {
                        System.out.println(Thread.currentThread().getName() + " 获取到两把锁");
                    } finally {
                        lock1.unlock();
                    }
                } finally {
                    lock2.unlock();
                }
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + " 被中断");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new InterruptibleLockDemo(1), "线程1");
        Thread t2 = new Thread(new InterruptibleLockDemo(2), "线程2");
        
        t1.start();
        t2.start();
        
        Thread.sleep(1000);
        // 中断线程2,打破死锁
        t2.interrupt();
    }
}

当线程在等待获取锁的过程中被中断时,lockInterruptibly()会抛出InterruptedException,从而可以优雅地处理中断情况。

# 四、超时机制

有时候我们希望在等待锁的时候设置一个超时时间,避免无限期等待。ReentrantLock提供了tryLock()方法来实现这一功能:

public class TimeoutLockDemo implements Runnable {
    private static final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            if (lock.tryLock(2, TimeUnit.SECONDS)) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取到锁,开始工作");
                    Thread.sleep(3000); // 模拟工作时间
                    System.out.println(Thread.currentThread().getName() + " 工作完成");
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " 获取锁超时,放弃执行");
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + " 被中断");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TimeoutLockDemo demo = new TimeoutLockDemo();
        Thread t1 = new Thread(demo, "线程1");
        Thread t2 = new Thread(demo, "线程2");
        
        t1.start();
        Thread.sleep(100); // 确保t1先启动
        t2.start();
        
        t1.join();
        t2.join();
    }
}

tryLock(long time, TimeUnit unit)方法允许线程在指定时间内尝试获取锁,超时后会放弃等待。这种机制在高并发场景下非常有用,可以避免线程长时间阻塞。

# 五、公平锁与非公平锁

默认情况下,ReentrantLock是非公平锁,即不保证等待时间最长的线程优先获取锁。这种策略虽然可能导致某些线程饥饿,但整体吞吐量更高。

public class FairLockDemo implements Runnable {
    // 创建公平锁
    private static final ReentrantLock fairLock = new ReentrantLock(true);
    // 创建非公平锁
    private static final ReentrantLock unfairLock = new ReentrantLock(false);

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 启动");
        
        for (int i = 0; i < 5; i++) {
            fairLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 获得公平锁");
            } finally {
                fairLock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        FairLockDemo demo = new FairLockDemo();
        
        // 启动多个线程测试公平锁
        for (int i = 0; i < 5; i++) {
            new Thread(demo, "线程" + i).start();
        }
    }
}

公平锁保证了按照请求锁的顺序获取锁,避免了饥饿现象,但由于需要维护队列,性能会有所下降。在实际应用中,需要根据具体场景选择合适的锁类型。

# 六、Condition:锁的得力助手

Condition接口提供了类似Object.wait()和Object.notify()的功能,但更加灵活。一个ReentrantLock可以创建多个Condition对象,实现更精确的线程通信。

public class ConditionDemo {
    private static final ReentrantLock lock = new ReentrantLock();
    private static final Condition condition = lock.newCondition();
    private static boolean ready = false;

    static class Worker implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                while (!ready) {
                    System.out.println(Thread.currentThread().getName() + " 等待条件满足");
                    condition.await();
                }
                System.out.println(Thread.currentThread().getName() + " 条件满足,开始工作");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 启动工作线程
        Thread worker1 = new Thread(new Worker(), "工作线程1");
        Thread worker2 = new Thread(new Worker(), "工作线程2");
        
        worker1.start();
        worker2.start();
        
        Thread.sleep(2000);
        
        // 唤醒等待的线程
        lock.lock();
        try {
            ready = true;
            condition.signalAll();
            System.out.println("主线程通知条件已满足");
        } finally {
            lock.unlock();
        }
    }
}

Condition的主要方法包括:

  • await():当前线程等待,直到被唤醒或中断
  • awaitUninterruptibly():当前线程等待,不响应中断
  • signal():唤醒一个等待线程
  • signalAll():唤醒所有等待线程

需要注意的是,调用Condition的方法前必须先获取对应的锁,否则会抛出IllegalMonitorStateException异常。

# 七、实际应用:ArrayBlockingQueue

JDK中的ArrayBlockingQueue就是ReentrantLock和Condition配合使用的经典案例:

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    
    /** 保护所有访问的主锁 */
    final ReentrantLock lock;
    
    /** 等待取元素的条件 */
    private final Condition notEmpty;
    
    /** 等待放元素的条件 */
    private final Condition notFull;

    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
    }

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await(); // 队列为空时等待
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await(); // 队列满时等待
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

    private void enqueue(E x) {
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal(); // 通知等待取元素的线程
    }
}

在这个实现中,ArrayBlockingQueue使用一个ReentrantLock和两个Condition对象:

  • notEmpty:当队列不为空时的条件
  • notFull:当队列不满时的条件

当队列为空时,take()操作会在notEmpty条件上等待;当队列满时,put()操作会在notFull条件上等待。通过这种方式实现了高效的生产者-消费者模式。

# 八、总结

ReentrantLock作为Java并发编程的重要工具,提供了比synchronized更丰富的功能:

  1. 可中断的锁获取:通过lockInterruptibly()方法实现
  2. 超时机制:通过tryLock()方法实现
  3. 公平锁支持:可以选择公平或非公平策略
  4. 多条件支持:通过Condition实现更精确的线程通信
  5. 锁状态查询:提供了丰富的锁状态查询方法

在选择锁机制时,如果只需要基本的互斥功能,synchronized依然是首选,因为它使用简单且JVM层面优化充分。但如果需要更高级的功能,如可中断、超时、公平性或者条件变量,那么ReentrantLock就是不二之选。


扩展阅读:深入理解AbstractQueuedSynchronizer (opens new window),了解ReentrantLock底层实现原理。

编辑 (opens new window)
#ReentrantLock#重入锁#并发编程
上次更新: 2025/08/14
Java并发-线程基础与synchronized关键字
Java并发-信号量Semaphore

← Java并发-线程基础与synchronized关键字 Java并发-信号量Semaphore→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式