目录
ReentrantLock是Java中实现Lock接口的一种重入锁(Reentrant Lock)实现类。它提供了与synchronized关键字相似的功能(在java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入),但比synchronized更加灵活和强大。ReentrantLock可以用于实现多线程之间的互斥访问和同步操作。
下面是ReentrantLock主要的特点和用法介绍:
1、可重入性:
ReentrantLock允许同一个线程多次获取同一把锁,而不会发生死锁。这种特性叫做可重入性,也就是可重入锁。当一个线程已经持有锁时,再次获取锁时会增加锁的计数器,每次释放锁时计数器减一,只有当计数器为0时锁才会完全释放。
2、获取锁的方式:
通过调用ReentrantLock的lock()方法可以获取锁,如果锁已被其他线程获取,则当前线程会被阻塞,直到获取到锁为止。另外,ReentrantLock还提供了tryLock()方法,该方法尝试立即获取锁,如果锁未被其他线程获取,则返回true,否则返回false。tryLock()方法也可以设置超时时间,指定在一段时间内尝试获取锁。
3、线程阻塞与唤醒:
与synchronized不同,ReentrantLock提供了显示的线程挂起和恢复的能力。通过调用lock()方法后,如果锁已被其他线程占用,则当前线程会进入休眠状态,直到获取锁成功。当不再需要锁时,需要调用unlock()方法显式地释放锁,以唤醒其他等待线程。
4、锁的公平性:
ReentrantLock支持公平锁和非公平锁。可以在创建ReentrantLock对象时指定构造函数参数来设置锁的公平性。默认情况下,ReentrantLock是非公平锁。
5、条件变量:
与ReentrantLock配套使用的还有条件变量(Condition),可以通过ReentrantLock的newCondition()方法创建一个特定的条件变量。条件变量可以使线程在某个条件满足时等待,或者唤醒等待在该条件上的线程。
总结:
ReentrantLock是Java提供的一种可重入的高级锁实现,相比synchronized关键字更加灵活,并提供了更多功能,如可控的锁公平性、显示的线程挂起和恢复、超时锁等等。在多线程编程中,使用ReentrantLock可以更好地控制资源的访问和线程的同步。。
那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:1. 重入性的实现原理;2. 公平锁和非公平锁。
ReentrantLock支持重入性的实现是通过在锁对象上维护一个计数器来实现的。
在Java中,每个线程都拥有自己的线程栈(Thread Stack)。如果一个方法被调用,那么就会在线程栈上分配一段空间,这段空间被称为栈帧(Stack Frame)。当一个方法被另一个方法调用时,就会在栈上分配另一个栈帧,这就形成了方法调用链。
在ReentrantLock中,每个线程都有一个变量叫做"holdCount",表示该线程目前获取锁的次数。当一个线程第一次获取锁时,holdCount的值为1;如果再次获取锁,则holdCount递增,以此类推。当这个线程释放锁时,holdCount递减。只有当holdCount的值为0时,锁才真正释放。
也就是说,ReentrantLock对于每个线程来说,维护了一个独立的计数器。当一个线程试图获取锁时,首先判断当前线程是否已经获取了该锁,如果已经获取,则直接返回true;如果未获取,则需要尝试获取锁。当该线程成功获取锁时,holdCount递增,表示当前线程又多获取了一次该锁。当该线程释放锁时,holdCount递减,表示当前线程又释放了一次该锁。只有当holdCount的值为0时,锁才会真正释放。
总结一下:在ReentrantLock中,通过为每个线程维护一个计数器(即holdCount)来实现重入性。当一个线程多次获取锁时,计数器递增;每次释放锁时,计数器相应地递减。只有当计数器为0时,锁才完全释放,其他线程才能获取该锁。这种机制可以避免死锁和线程饥饿等问题,是一种非常有效的并发控制手段。
- import java.util.concurrent.locks.ReentrantLock;
-
- public class Main {
- private ReentrantLock lock = new ReentrantLock();
- private double balance;
-
- public Main(double initialBalance) {
- balance = initialBalance;
- }
-
- // 存款
- public void deposit(double amount) {
- lock.lock(); // 获取锁
- try {
- balance += amount; // 更新余额
- System.out.println("存款成功,当前余额为:" + balance);
- } finally {
- lock.unlock(); // 释放锁
- }
- }
-
- // 取款
- public void withdraw(double amount) {
- lock.lock(); // 获取锁
- try {
- if (balance >= amount) {
- balance -= amount; // 更新余额
- System.out.println("取款成功,当前余额为:" + balance);
- } else {
- System.out.println("余额不足");
- }
- } finally {
- lock.unlock(); // 释放锁
- }
- }
-
- public static void main(String[] args) {
- Main account = new Main(1000.0);
-
- // 创建两个线程模拟两个用户进行存款和取款操作
- Thread depositThread = new Thread(() -> {
- for (int i = 0; i < 5; i++) {
- account.deposit(100.0);
- }
- });
-
- Thread withdrawThread = new Thread(() -> {
- for (int i = 0; i < 5; i++) {
- account.withdraw(200.0);
- }
- });
-
- depositThread.start();
- withdrawThread.start();
- }
- }
在这个例子中,我们模拟了一个银行账户,通过使用ReentrantLock实现对余额的线程安全操作。在存款和取款方法中,我们都先获取锁,执行相应的操作,然后释放锁。
在主函数中,我们创建了两个线程,一个用于模拟存款操作,另一个用于模拟取款操作。每个线程都执行5次操作。通过使用ReentrantLock,我们确保了每次存款或取款操作是原子的,不会发生并发访问的问题。同时,由于ReentrantLock支持重入性,所以同一个线程可以多次获取锁,这也是允许的。
公平锁和非公平锁是ReentrantLock(可重入锁)中的两种获取锁的策略。
ReentrantLock默认是非公平锁,因为在大多数情况下,非公平锁性能更好。如果需要创建公平锁,可以通过构造函数参数传入true,创建一个公平锁的实例:
- ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁
- //注意:使用ReentrantLock类创建非公平锁时,不需要传入参数或者传入false
需要注意的是,公平锁会增加一定的上下文切换次数,适用于对线程获取锁的顺序有严格要求的场景,而非公平锁适用于追求更高吞吐量的场景。
总结:
在实际应用中,选择公平锁还是非公平锁取决于具体的场景和需求。一般来说,在线程竞争激烈的情况下,非公平锁可能更适合,可以提升系统性能。
- import java.util.concurrent.locks.ReentrantLock;
-
- public class Main {
- private static ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁
- private static ReentrantLock nonFairLock = new ReentrantLock(false); // 创建非公平锁
-
- public static void main(String[] args) {
- // 公平锁示例
- Thread fairThread1 = new Thread(() -> {
- fairLock.lock(); // 获取公平锁
- try {
- System.out.println("公平锁 - 线程1获取到锁");
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- fairLock.unlock(); // 释放公平锁
- System.out.println("公平锁 - 线程1 释放公平锁");
- }
- });
-
- Thread fairThread2 = new Thread(() -> {
- fairLock.lock(); // 获取公平锁
- try {
- System.out.println("公平锁 - 线程2获取到锁");
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- fairLock.unlock(); // 释放公平锁
- System.out.println("公平锁 - 线程2 释放公平锁");
- }
- });
-
- // 非公平锁示例
- Thread nonFairThread1 = new Thread(() -> {
- nonFairLock.lock(); // 获取非公平锁
- try {
- System.out.println("非公平锁 - 线程1获取到锁");
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- nonFairLock.unlock(); // 释放非公平锁
- System.out.println("非公平锁 - 线程1 释放非公平锁");
- }
- });
-
- Thread nonFairThread2 = new Thread(() -> {
- nonFairLock.lock(); // 获取非公平锁
- try {
- System.out.println("非公平锁 - 线程2获取到锁");
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- nonFairLock.unlock(); // 释放非公平锁
- System.out.println("非公平锁 - 线程2 释放非公平锁");
- }
- });
-
- fairThread1.start();
- fairThread2.start();
- nonFairThread1.start();
- nonFairThread2.start();
- }
- }
在上述示例中,我们创建了一个公平锁(fairLock)和一个非公平锁(nonFairLock),然后创建了两个线程来演示这两种锁的行为。
通过运行上述代码,我们可以观察到公平锁和非公平锁的不同行为。请注意,输出结果可能因操作系统调度而有所差异,但公平锁会按照线程的请求顺序获取锁,而非公平锁则不保证顺序。