博主打算从0-1讲解下java进阶篇教学,今天教学第十篇:Java中ReentrantLock锁讲解。
在Java并发编程中,保证多线程环境下的数据安全是至关重要的。ReentrantLock 是Java中用于实现线程安全的一种锁机制。本篇博客将深入介绍 ReentrantLock 的原理、详细说明,并通过案例演示线程不安全情况以及如何使用 ReentrantLock 实现线程安全。
目录
一、原理
二、代码测试
1.线程不安全案例
2.线程安全案例
三、公平性
四、条件变量
一、原理
ReentrantLock 是Java中的一种锁实现,它具有可重入性,即同一线程可以多次获取同一把锁而不会出现死锁。它使用了一种互斥锁的机制,确保了在同一时刻只有一个线程可以访问被锁定的代码块或方法。
ReentrantLock是 Java 中的一个可重入锁类,它实现了Lock接口。ReentrantLock的原理主要涉及以下几个方面:
- 锁状态:ReentrantLock通过一个内部的锁状态来表示当前锁的占用情况。锁状态可以是未锁定、锁定和重入锁定等状态。
- 获取锁:当线程调用lock方法时,它会尝试获取锁。如果锁当前没有被其他线程占用,那么该线程将成功获取锁,并将锁状态设置为锁定。如果锁已经被其他线程占用,那么当前线程将被阻塞,直到锁被释放。
- 释放锁:当线程调用unlock方法时,它会释放锁。如果当前线程持有锁,那么它将把锁状态设置为未锁定,并唤醒等待获取锁的线程。
- 可重入性:ReentrantLock支持可重入性,即同一个线程可以多次获取同一个锁。在获取锁时,锁的持有计数会增加,在释放锁时,锁的持有计数会减少。只有当锁的持有计数为 0 时,锁才会被完全释放。
- 公平性:ReentrantLock可以选择是否采用公平锁策略。公平锁保证线程按照先来先服务的顺序获取锁,而不公平锁则允许线程抢占锁。
- 条件变量:ReentrantLock还提供了条件变量的支持,可以用于实现线程的等待和通知机制。线程可以在满足特定条件时等待,直到其他线程通知条件满足。
二、代码测试
1.线程不安全案例
public class UnsafeCounter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
public static void main(String[] args) {
UnsafeCounter counter = new UnsafeCounter();
// 创建两个线程并发增加计数
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
// 等待两个线程执行完成
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出最终计数值
System.out.println("Count: " + counter.getCount()); // 预期结果: 可能小于 2000
}
}
在这个示例中,由于 increment() 方法没有同步控制,两个线程同时对 count 进行增加操作,可能导致计数不准确。得到的结果偶尔可能是正确的2000。
2.线程安全案例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SafeCounter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
SafeCounter counter = new SafeCounter();
// 创建两个线程并发增加计数
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
// 等待两个线程执行完成
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出最终计数值
System.out.println("Count: " + counter.getCount()); // 预期结果: 可能小于 2000
}
}
在这个示例中,使用 ReentrantLock 来确保了对 count 的操作是线程安全的,保证了最终输出的计数值是准确的。
三、公平性
公平性是指在多线程环境下,锁的获取顺序应该遵循先来先服务的原则,即先请求锁的线程应该先获得锁。在 Java 中,可以通过设置ReentrantLock的构造函数参数来选择使用公平锁或非公平锁。以下是一个使用公平锁的示例代码:
public class ReentrantLockTest extends Thread {
private static ReentrantLock lock = new ReentrantLock(true); // 创建公平锁
@Override
public void run() {
for (int i = 0; i < 100; i++) {
lock.lock(); // 获取锁
try {
System.out.println(Thread.currentThread().getName() + " 获得锁");
// 执行临界区操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
}
public static void main(String[] args) {
ReentrantLockTest thread1 = new ReentrantLockTest();
ReentrantLockTest thread2 = new ReentrantLockTest();
ReentrantLockTest thread3 = new ReentrantLockTest();
thread1.start();
thread2.start();
thread3.start();
}
}
默认情况下非公平锁
Lock lock = new ReentrantLock();
创建公平锁
Lock fairLock = new ReentrantLock(true);
四、条件变量
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionVariableExample {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void waitForCondition() {
try {
lock.lock();
System.out.println("线程等待条件满足...");
condition.await();
System.out.println("线程收到通知,条件满足!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void notifyCondition() {
try {
lock.lock();
System.out.println("通知线程,条件满足...");
condition.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionVariableExample example = new ConditionVariableExample();
// 创建并启动等待条件的线程
Thread waitingThread = new Thread(() -> {
example.waitForCondition();
});
waitingThread.start();
// 模拟条件满足的情况
// 可以在其他地方执行此操作,以通知等待线程条件已满足
example.notifyCondition();
}
}
在上述示例中,我们创建了一个ReentrantLock对象lock和一个与之关联的条件变量condition。
- waitForCondition方法用于线程等待条件满足。在方法内部,首先获取锁,然后打印出等待消息,并使用condition.await方法使线程等待条件满足。在等待过程中,线程会释放锁,并进入阻塞状态。
- notifyCondition方法用于通知等待条件的线程。在方法内部,获取锁后,打印出通知消息,并使用condition.signalAll方法通知所有等待条件的线程。
在main方法中,我们创建了一个等待条件的线程waitingThread,并启动它。然后,模拟条件满足的情况,调用notifyCondition方法通知等待线程。
通过使用ReentrantLock和条件变量,我们可以实现线程之间的同步和协作,确保在特定条件满足时执行相应的操作。