ReentrantLock 使用详解

 

ReentrantLock 使用详解

 

 

1、简介

ReentrantLock是一种可重入的独占锁,它允许同一个线程多次获取同一个锁而不会被阻塞。它的功能类似于synchronized是一种互斥锁,可以保证线程安全。相对于 synchronized,

ReentrantLock具备如下特点:
  • 可中断。
  • 可以设置超时时间。
  • 可以设置为公平锁。
  • 支持多个条件变量。

 

2、常用API介绍

  • void lock() : 获取锁,调用该方法当前线程会获取锁,当锁获得后,该方法返回。
  • void lockInterruptibly() throws InterruptedException: 在锁的获取中可以中断当前线程。
  • boolean tryLock(): 尝试非阻塞的获取锁,调用该方法后立即返回。如果能够获取到返回true,否则返回false。
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException: 超时获取锁,当前线程在以下三种情况下会被返回:1、当前线程在超时时间内获取了锁。2、当前线程在超时时间内被中断。3、超时时间结束,返回false。
  • void unlock(): 释放锁。
  • Condition newCondition(): 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法,而调用后,当前线程将释放锁。

 使用示例:

private final Lock lock = new ReentrantLock();
  public void foo() {
   // 获取锁    
   lock.lock();    
     try {        
       // 程序执行逻辑  
    } finally { 
     // finally语句块可以确保lock被正确释放       
    lock.unlock();    
  }
 }
在使用时要注意 4 个问题:
  • 1.默认情况下 ReentrantLock 为非公平锁而非公平锁;
  • 2.加锁次数和释放锁次数一定要保持一致,否则会导致线程阻塞或程序异常;
  • 3.加锁操作一定要放在 try 代码之前,这样可以避免未加锁成功又释放锁的异常;
  • 4.释放锁一定要放在 finally 中,否则会导致线程阻塞。
公平锁和非公平锁
ReentrantLock支持公平锁和非公平锁两种模式:
  • 公平锁:线程在获取锁时,按照等待的先后顺序获取锁。
  • 非公平锁:线程在获取锁时,不按照等待的先后顺序获取锁,而是随机获取锁。ReentrantLock默认是非公平锁
ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁  
ReentrantLock lock = new ReentrantLock(true);
//
公平锁
 
工作原理
当有线程调用lock方法的时候: 如果线程获取到锁了,那么就会通过CAS的方式把AQS内部的state设置成为1。这个时候,当前线程就获取到锁了。只有首部的节点(head节点封装的线程)可以获取到锁。其他线程都会加入到这一个阻塞队列当中。如果是公平锁的话,当head节点释放锁之后,会优先唤醒head.next这一个节点对应的线程。如果是非公平锁,允许新来的线程和head之后唤醒的线程通过cas竞争锁。
 
可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。在实际开发中,可重入锁常常应用于递归操作、调用同一个类中的其他方法、锁嵌套等场景中。
 

3、Condition详解

在Java中,Condition是一个接口,它提供了线程之间的协调机制,可以将它看作是一个更加灵活、更加强大的wait()和notify()机制,通常与Lock接口(比如ReentrantLock)一起使用。它的核心作用体现在两个方面,如下:

等待/通知机制:它允许线程等待某个条件成立,或者通知其他线程某个条件已经满足,这与使用Object的wait()和notify()方法相似,但Condition提供了更高的灵活性和更多的控制。
多条件协调:与每个Object只有一个内置的等待/通知机制不同,一个Lock可以对应多个Condition对象,这意味着可以为不同的等待条件创建不同的Condition,从而实现对多个等待线程集合的独立控制。
 
方法介绍:
  • void await():使当前线程等待,直到被其他线程通过 signal() 或 signalAll() 方法唤醒,或者线程被中断,或者发生了其他不可预知的情况(如假唤醒)。该方法会在等待之前释放当前线程所持有的锁,在被唤醒后会再次尝试获取锁。
  • boolean await(long time, TimeUnit unit):使当前线程等待指定的时间,或者直到被其他线程通过 signal() 或 signalAll() 方法唤醒,或者线程被中断。如果在指定的时间内没有被唤醒,该方法将返回。在等待之前会释放当前线程所持有的锁,在被唤醒或超时后会再次尝试获取锁。
  • void signal():唤醒等待在此 Condition 上的一个线程。如果有多个线程正在等待,则选择其中的一个进行唤醒。被唤醒的线程将从其 await() 调用中返回,并重新尝试获取与此 Condition 关联的锁。
  • void signalAll():唤醒等待在此 Condition 上的所有线程。每个被唤醒的线程都将从其 await() 调用中返回,并重新尝试获取与此 Condition 关联的锁。
java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或 Condition.signalAll() 方法唤醒等待的线程。
注意:调用Condition的await()和signal()方法,都必须在lock保护之内。
 
 案例:实现简单的生产者、消费者模型
public class MyQueue {

    private Object[] items;
    int size = 0;
    int takeIndex;
    int putIndex;
    private ReentrantLock lock;
    public Condition notEmpty; //消费者线程阻塞唤醒条件,队列为空阻塞,生产者生产完唤醒
    public Condition notFull; //生产者线程阻塞唤醒条件,队列满了阻塞,消费者消费完唤醒

    public MyQueue(int capacity) {
        this.items = new Object[capacity];
        lock = new ReentrantLock();
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
    }

    public void put(Object value) throws Exception {
        //加锁
        lock.lock();
        try {
            while (size == items.length)
                // 队列满了让生产者等待
                notFull.await();
            items[putIndex] = value;
            if (++putIndex == items.length)
                putIndex = 0;
            size++;
            // 生产完唤醒消费者
            notEmpty.signal();
        } finally {
            System.out.println("producer生产:" + value);
            //解锁
            lock.unlock();
        }
    }

    public Object take() throws Exception {
        lock.lock();
        try {
            // 队列空了就让消费者等待
            while (size == 0)
                notEmpty.await();
            Object value = items[takeIndex];
            items[takeIndex] = null;
            if (++takeIndex == items.length)
                takeIndex = 0;
            size--;
            notFull.signal();
            //消费完唤醒生产者生产
            return value;
        } finally {
            lock.unlock();
        }
    }

    static class Producer implements Runnable {
        private MyQueue queue;

        public Producer(MyQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                // 隔1秒轮询生产一次
                while (true) {
                    Thread.sleep(1000);
                    queue.put(new Random().nextInt(1000));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static class Customer implements Runnable {
        private MyQueue queue;

        public Customer(MyQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                // 隔2秒轮询消费一次
                while (true) {
                    Thread.sleep(2000);
                    System.out.println("consumer消费:" + queue.take());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // 创建队列  
        MyQueue queue = new MyQueue(5);      
         //启动生产者线程       
         new Thread(new Producer(queue)).start();        
        // 启动消费者线程      
         new Thread(new Customer(queue)).start();
    }

}

 

 
 
 
 
 
 
 

 

posted @ 2025-12-04 12:02  邓维-java  阅读(9)  评论(0)    收藏  举报