1.ReentrantLock概念
ReentrantLock
是 Java 中的一种可重入锁,属于 java.util.concurrent.locks
包。它提供了比 synchronized
关键字更灵活的锁机制,允许更复杂的线程同步控制。以下是 ReentrantLock
的一些关键概念和特点:
-
可重入性:同一个线程可以多次获得同一把锁,而不会导致死锁。这意味着如果一个线程已经持有了锁,它可以再次获得该锁而不被阻塞。
-
显式锁定:与
synchronized
不同,ReentrantLock
需要手动锁定和解锁。使用lock()
方法来锁定,使用unlock()
方法来解锁。 -
公平锁与非公平锁:
ReentrantLock
可以选择是否使用公平性策略。公平锁按照请求锁的顺序分配锁,而非公平锁则不保证顺序,可能会导致某些线程饥饿。 -
条件变量:
ReentrantLock
允许使用条件变量(Condition
),可以实现更复杂的线程通信机制。通过newCondition()
方法创建条件变量,可以使用await()
和signal()
等方法进行线程之间的协调。 -
性能:在某些情况下,
ReentrantLock
的性能可能优于synchronized
,尤其是在高竞争的场景中,因为它采用了更复杂的算法来管理锁的获取和释放(可以进行更低粒度锁的控制)。
2.基本使用Demo
2.1 lock、unlock方法-Demo
public class ReentrantLockDemo01 {
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();// 默认非公平锁
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + " 成功获取锁");
try {
System.out.println(Thread.currentThread().getName() + ".....");
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
reentrantLock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放锁");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);// 确保thread1先执行,thread1进行锁的获取和释放
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " 尝试获取锁");
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + " 成功获取锁");
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ".....");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " 释放锁");
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(Thread.currentThread().getName() + " 执行了");
}
}
输出:
Thread-0首先获取锁,然后模拟执行2s业务,之后释放锁;
Thread-1首先睡眠1s,然后获取锁,但是被阻塞;等Thread-0释放锁后,Thread-1成功获取锁,执行业务逻辑。
2.2 可重入性-Demo
public class ReentrantLockDemo02 {
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();// 默认非公平锁
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
reentrantLock.lock();
try{
System.out.println(Thread.currentThread().getName() + " start");
reentrantLock.lock();// 可重入
System.out.println();
System.out.println(Thread.currentThread().getName() + " reStart");
System.out.println(Thread.currentThread().getName() + " end");
}finally {
reentrantLock.unlock();
}
}
});
thread1.start();
thread1.join();
System.out.println(Thread.currentThread().getName() + " 执行了");
}
}
输出:
2.3 公平及非公平锁
默认为非公平锁(ReentrentLock的构成方法)
2.4 可中断性-Demo
public class ReentrantLockWithInterruptorDemo {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
// t1先获取锁
Thread t1 = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " 启动了");
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + " 获取到锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("t1报错");
throw new RuntimeException(e);
}
} finally {
reentrantLock.unlock();
}
System.out.println(Thread.currentThread().getName() + " 释放锁");
});
t1.start();
// 测试重点: t2后获取锁。测试lock、lockInterruptibly方法
Thread t2 = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " 启动了");
// 确保t2先执行性
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("t2报错");
throw new RuntimeException(e);
}
try {
long l = System.currentTimeMillis();
// reentrantLock.lock();
reentrantLock.lockInterruptibly();
long l1 = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " 等待获取锁的时间"+(l1-l));
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 获取锁失败,t2需要中断,t2要退出啦");
throw new RuntimeException(e);
} finally {
reentrantLock.unlock();
}
});
t2.start();
Thread t3 = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " 启动了");
Thread.sleep(2000);
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " 报错");
throw new RuntimeException(e);
}
t2.interrupt();// 中断t2
System.out.println("t3中断t2了");
});
t3.start();
}
}
输出:在Thread-1中,调用reentrantLock.lockInterruptibly();,lockInterruptibly表示该线程如果获取锁失败,则接收中断。在Thread-2中,主动中断Thread-1,所以Thread-1报出异常。
2.5 Condition条件变量-Demo
ReentrantLock
允许使用条件变量(Condition
),可以实现更复杂的线程通信机制。通过 newCondition()
方法创建条件变量,可以使用 await()
和 signal()
等方法进行线程之间的协调。
通过Condition模拟实现生产者-消费者模型:
仓库类:
/**
* 仓库类
*/
public class Depot {
private int capacity; // 仓库的容量
private int size; // 仓库的实际数量
private ReentrantLock lock; // 可重入锁
private Condition fullCondtion; // 生产条件
private Condition emptyCondtion; // 消费条件
/**
* 初始化
*/
public Depot() {
capacity = 100;
size = 0;
lock = new ReentrantLock();
fullCondtion = lock.newCondition();
emptyCondtion = lock.newCondition();
}
/**
* 生产流程
* @param val:生产的个数
*/
public void produce(int val) {
try {
lock.lock();
System.out.println("生产者:" + Thread.currentThread().getName() + "开始执行");
//left:还需生产的个数
int left = val;
while (left > 0) {
//当仓库满了,需要暂停生产
if (size >= capacity) {
System.out.println(Thread.currentThread().getName() + ":当前仓库已满,生产进程进入等待");
//当前生产进程阻塞
fullCondtion.await();
}
// incr:此次生产的数量
int incr = (capacity - size) > left ? left : (capacity - size);
size = size + incr;
left = left - incr;
System.out.println(Thread.currentThread().getName() + "生产:incr=" + incr + ",left=" + left + ",size=" + size);
// 生产完了,唤起消费进程
emptyCondtion.signal();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
/**
* 消费流程
*
* @param val:消费的个数
*/
public void consume(int val) {
try {
lock.lock();
System.out.println("消费者:" + Thread.currentThread().getName() + "开始执行");
//left:剩余消费的数量
int left = val;
while (left > 0) {
if (size <= 0) {
System.out.println(Thread.currentThread().getName() + ":当前仓库已空,消费进程进入等待");
emptyCondtion.await();
}
// dec : 此次消费的个数
int dec = size > left ? left : size;
left = left - dec;
size = size - dec;
System.out.println(Thread.currentThread().getName() + "消费:dec=" + dec + ",left=" + left + ",size=" + size);
// 唤醒生产进程
fullCondtion.signal();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
生产者:
/**
* 生产者
*/
public class Producer {
private Depot depot;
public Producer(Depot depot){
this.depot= depot;
}
public void produce(int val){
new Thread(() -> {
depot.produce(val);
}).start();
}
}
消费者:
/**
* 消费者
*/
public class Consumer {
private Depot depot;
public Consumer(Depot depot){
this.depot = depot;
}
public void consume(int val){
new Thread(){
@Override
public void run() {
depot.consume(val);
}
}.start();
}
}
测试类:
public class Main {
public static void main(String[] args) {
Depot depot = new Depot();
Producer producer = new Producer(depot);
Consumer consumer = new Consumer(depot);
consumer.consume(150);
producer.produce(200);
producer.produce(100);
consumer.consume(150);
consumer.consume(50);
producer.produce(200);
}
}
结果输出:
说明:在仓库类中定义生产者和消费者共用的ReentrentLock及Condition。当调用消费者的时候,首先竞争Lock,然后调用Condition.await方法竞争Condition资源,进行消费,当仓库产品为空时,唤醒生产者,消费者释放锁。生产者首先竞争Lock,然后调用Condition.await()方法竞争,开始生产产品,当仓库为满时,通过Condition.signal()唤醒消费者进行消费。