juc之ReentrantLock

ReentrantLock 是 Java 里用于实现线程同步的工具,它在 java.util.concurrent.locks 包中,是 Lock 接口的一个具体实现。和 synchronized 关键字相比,ReentrantLock 更灵活、功能更丰富。下面为你详细介绍它的使用方法:

1. 基本使用

基本使用步骤如下:

  • 创建 ReentrantLock 实例。
  • 在需要同步的代码块前调用 lock() 方法获取锁。
  • 同步代码块执行完毕后,调用 unlock() 方法释放锁。

以下是示例代码:

java 复制代码
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
            System.out.println(Thread.currentThread().getName() + " 增加后的值: " + count);
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.increment();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();
    }
}

在这个示例中,increment 方法借助 ReentrantLock 保证线程安全。lock.lock() 用来获取锁,lock.unlock() 用来释放锁。为确保锁一定会被释放,unlock() 方法要放在 finally 块中。

2. 可重入性

ReentrantLock 具备可重入性,这意味着同一个线程能够多次获取同一把锁,而不会产生死锁。下面是示例代码:

java 复制代码
import java.util.concurrent.locks.ReentrantLock;

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

    public void outerMethod() {
        lock.lock();
        try {
            System.out.println("外层方法获取锁");
            innerMethod();
        } finally {
            lock.unlock();
        }
    }

    public void innerMethod() {
        lock.lock();
        try {
            System.out.println("内层方法获取锁");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantExample example = new ReentrantExample();
        example.outerMethod();
    }
}

在这个示例中,outerMethod 方法获取锁之后,调用了 innerMethod 方法,innerMethod 方法又一次获取了同一把锁,这就是可重入性的体现。

3. 公平锁

ReentrantLock 可以创建公平锁,公平锁会按照线程请求锁的顺序来分配锁。创建公平锁时,需要在构造函数中传入 true。示例代码如下:

java 复制代码
import java.util.concurrent.locks.ReentrantLock;

public class FairLockExample {
    private final ReentrantLock fairLock = new ReentrantLock(true);

    public void doSomething() {
        fairLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 获取到公平锁");
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            fairLock.unlock();
        }
    }

    public static void main(String[] args) {
        FairLockExample example = new FairLockExample();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> example.doSomething()).start();
        }
    }
}

在这个示例中,通过 new ReentrantLock(true) 创建了公平锁,线程会按照请求锁的顺序依次获取锁。

4. 尝试获取锁

ReentrantLock 提供了 tryLock() 方法,该方法可尝试获取锁,若锁可用则获取并返回 true,若不可用则立即返回 false,不会阻塞线程。示例代码如下:

java 复制代码
import java.util.concurrent.locks.ReentrantLock;

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

    public void tryLockMethod() {
        if (lock.tryLock()) {
            try {
                System.out.println(Thread.currentThread().getName() + " 获取到锁");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " 未能获取到锁");
        }
    }

    public static void main(String[] args) {
        TryLockExample example = new TryLockExample();

        Thread t1 = new Thread(() -> example.tryLockMethod());
        Thread t2 = new Thread(() -> example.tryLockMethod());

        t1.start();
        t2.start();
    }
}

在这个示例中,tryLock() 方法会尝试获取锁,若获取失败则会执行 else 块中的代码。

5. 带超时的尝试获取锁

ReentrantLock 还提供了 tryLock(long timeout, TimeUnit unit) 方法,该方法可以在指定的时间内尝试获取锁,若在超时时间内获取到锁则返回 true,否则返回 false。示例代码如下:

java 复制代码
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

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

    public void tryLockWithTimeout() {
        try {
            if (lock.tryLock(1, TimeUnit.SECONDS)) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 在超时时间内获取到锁");
                    Thread.sleep(2000);
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " 在超时时间内未能获取到锁");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        TryLockWithTimeoutExample example = new TryLockWithTimeoutExample();

        Thread t1 = new Thread(() -> example.tryLockWithTimeout());
        Thread t2 = new Thread(() -> example.tryLockWithTimeout());

        t1.start();
        t2.start();
    }
}

在这个示例中,tryLock(1, TimeUnit.SECONDS) 方法会在 1 秒内尝试获取锁,若 1 秒内未获取到锁则返回 false


在 Java 中,ReentrantLock 类的 newCondition() 方法用于创建一个 Condition 对象。Condition 接口为线程提供了一种更灵活的等待/通知机制,它可以替代传统的 Object 类的 wait()notify()notifyAll() 方法。下面详细介绍 newCondition() 的用途和使用方式。

用途

  • 线程协作Condition 允许线程在某个条件不满足时等待,当条件满足时被其他线程唤醒。这有助于实现线程之间的协作,避免了传统 wait()notify() 方法的一些局限性,例如可以创建多个等待队列。
  • 精确控制 :可以为不同的条件创建不同的 Condition 对象,实现更精确的线程唤醒和等待控制。比如,一个生产者 - 消费者模型中,可以有一个 Condition 用于生产者等待缓冲区有空间,另一个 Condition 用于消费者等待缓冲区有元素。

使用示例

下面通过一个简单的生产者 - 消费者模型来展示 newCondition() 的使用:

java 复制代码
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int capacity = 5;
    private final ReentrantLock lock = new ReentrantLock();
    // 用于生产者等待的条件
    private final Condition notFull = lock.newCondition(); 
    // 用于消费者等待的条件
    private final Condition notEmpty = lock.newCondition(); 

    public void produce() throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                // 缓冲区满,生产者等待
                notFull.await(); 
            }
            int item = (int) (Math.random() * 100);
            queue.add(item);
            System.out.println("Produced: " + item);
            // 通知消费者缓冲区有元素了
            notEmpty.signal(); 
        } finally {
            lock.unlock();
        }
    }

    public void consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                // 缓冲区空,消费者等待
                notEmpty.await(); 
            }
            int item = queue.poll();
            System.out.println("Consumed: " + item);
            // 通知生产者缓冲区有空间了
            notFull.signal(); 
        } finally {
            lock.unlock();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();

        Thread producer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    pc.produce();
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread consumer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    pc.consume();
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        producer.start();
        consumer.start();
    }
}

代码解释

  1. 创建 Condition 对象

    • private final Condition notFull = lock.newCondition();:创建一个 Condition 对象 notFull,用于表示缓冲区不满的条件。
    • private final Condition notEmpty = lock.newCondition();:创建一个 Condition 对象 notEmpty,用于表示缓冲区不为空的条件。
  2. 生产者逻辑

    • produce() 方法中,当缓冲区满时,调用 notFull.await() 使生产者线程等待。
    • 当生产一个元素后,调用 notEmpty.signal() 唤醒一个等待的消费者线程。
  3. 消费者逻辑

    • consume() 方法中,当缓冲区为空时,调用 notEmpty.await() 使消费者线程等待。
    • 当消费一个元素后,调用 notFull.signal() 唤醒一个等待的生产者线程。

通过这种方式,Condition 对象实现了生产者和消费者线程之间的精确协作。

相关推荐
lgily-12252 小时前
常用的设计模式详解
java·后端·python·设计模式
意倾城3 小时前
Spring Boot 配置文件敏感信息加密:Jasypt 实战
java·spring boot·后端
火皇4053 小时前
Spring Boot 使用 OSHI 实现系统运行状态监控接口
java·spring boot·后端
薯条不要番茄酱3 小时前
【SpringBoot】从零开始全面解析Spring MVC (一)
java·spring boot·后端
懵逼的小黑子11 小时前
Django 项目的 models 目录中,__init__.py 文件的作用
后端·python·django
小林学习编程12 小时前
SpringBoot校园失物招领信息平台
java·spring boot·后端
java1234_小锋14 小时前
Spring Bean有哪几种配置方式?
java·后端·spring
柯南二号15 小时前
【后端】SpringBoot用CORS解决无法跨域访问的问题
java·spring boot·后端
每天一个秃顶小技巧16 小时前
02.Golang 切片(slice)源码分析(一、定义与基础操作实现)
开发语言·后端·python·golang
gCode Teacher 格码致知17 小时前
《Asp.net Mvc 网站开发》复习试题
后端·asp.net·mvc