ReentrantLock 是Java中提供的一种可重入锁(Reentrant Lock),用于在多线程环境下实现对共享资源的互斥访问。与 synchronized 关键字相比,ReentrantLock 提供了更灵活、更强大的功能,同时也更复杂。
可重入锁是一种同步机制,它允许同一个线程多次获取同一个锁而不会造成死锁。当线程第一次获得锁后,可以多次重复进入临界区,而不会被阻塞。只有当线程释放了所有重复获得的锁,其他线程才能够获取该锁。当然 synchronized 关键字实现的锁也是可重入的。
ReentrantLock 的主要作用如下:
直接使用 ReentrantLock 的 lock 和 unlcok 方法,基本功能同 synchronized 关键字。但是他比 synchronized 强大的地方在于他可以设置为公平锁和非公平锁,以及使用可中断获取锁和超时获取锁方法。
class XXXXXX {
private ReentrantLock lock = new ReentrantLock(true); // 公平锁
//private ReentrantLock lock = new ReentrantLock(false); // 非公平锁
public void increment() {
lock.lock(); // 获取锁
// lock.lockInterruptibly(); // 获取可中断的锁
// lock.tryLock(3000, TimeUnit.SECONDS);// 在指时间内获取锁
try {
// 业务逻辑
} finally {
lock.unlock();// 释放锁
}
}
}
lockInterruptibly() 方法说明:
如果当前线程未被中断,并且锁是可用的,则该方法立即获取锁并返回。
如果当前线程未被中断,但是锁当前被其他线程占用,则当前线程进入阻塞状态,等待锁的释放。
如果当前线程在等待锁的过程中被中断,则抛出
InterruptedException
异常。
ReentrantLock 的条件变量(Condition)的使用,实现线程等待/通知机制。
class BoundedQueue<T> {
private Queue<T> queue = new LinkedList<>();
private int capacity;
private ReentrantLock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BoundedQueue(int capacity) {
this.capacity = capacity;
}
public void enqueue(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
// 条件1达到, 业务逻辑1等待
notFull.await();
}
queue.add(item);
// 通知业务逻辑2执行
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public T dequeue() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
// 条件2达到,业务逻辑2等等
notEmpty.await();
}
T item = queue.poll();
// 通知业务逻辑1执行
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
}
定义一个Counter类,用于在多线程环境下进行计数。使用ReentrantLock来保护count变量的访问。increment()方法和getCount()方法获取锁来确保对count的操作是线程安全的。
class Counter {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
测试方法:开启20个线程同时计数,在多线程条件下正确结果为20000。
private void testCounter(){
Counter counter = new Counter();
List<Thread> threads = new ArrayList();
for(int i = 0; i< 20; i++){
Thread t = new Thread(()->{
for(int j=0; j< 1000; j++){
counter.increment();
}
});
threads.add(t);
}
for(Thread t : threads){
t.start();
}
for(Thread t : threads){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("最终结果:"+counter.getCount());
}
BoundedQueue类实现了一个有界队列,使用ReentrantLock和条件变量来实现线程安全的入队和出队操作。当队列已满时,入队线程会等待notFull条件成立;当队列为空时,出队线程会等待notEmpty条件成立。
class BoundedQueue<T> {
private Queue<T> queue = new LinkedList<>();
private int capacity;
private ReentrantLock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BoundedQueue(int capacity) {
this.capacity = capacity;
}
public void enqueue(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await();
}
queue.add(item);
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public T dequeue() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
T item = queue.poll();
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
}
测试方法:创建一组生产线程,一组消费线程。不管生产线程和消费线程的数量如何变化,以及生过程和消费过程业务用时长短(用sleep模拟),他们总能保持生产一定数量后消费一定数量,不会一直生产,也不会一直消费。
private void testCondition(){
BoundedQueue<String> queue = new BoundedQueue<String>(10);
List<Thread> threads = new ArrayList();
for(int i = 1; i<= 5; i++){
Thread t = new Thread(()->{
for(int j=0; j< 100000; j++){
try {
String text = Thread.currentThread().getName()+" 生产"+j;
queue.enqueue(text);
System.out.println(text);
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setName("生产线程"+i);
threads.add(t);
}
for(int i = 1; i<= 10; i++){
Thread t = new Thread(()->{
for(int j=0; j< 100000; j++){
try {
String result = queue.dequeue();
System.out.println(Thread.currentThread().getName()+" 消费 "+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setName("消费线程"+i);
threads.add(t);
}
for(Thread t : threads){
t.start();
}
for(Thread t : threads){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
完整测试代码如下
package top.yiqifu.study.p004_thread;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Test061_ReentrantLock {
public static void main(String[] args) {
Test061_ReentrantLock test = new Test061_ReentrantLock();
test.testCounter();
test.testCondition();
}
private void testCounter(){
Counter counter = new Counter();
List<Thread> threads = new ArrayList();
for(int i = 0; i< 20; i++){
Thread t = new Thread(()->{
for(int j=0; j< 1000; j++){
counter.increment();
}
});
threads.add(t);
}
for(Thread t : threads){
t.start();
}
for(Thread t : threads){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("最终结果:"+counter.getCount());
}
private void testCondition(){
BoundedQueue<String> queue = new BoundedQueue<String>(10);
List<Thread> threads = new ArrayList();
for(int i = 1; i<= 5; i++){
Thread t = new Thread(()->{
for(int j=0; j< 100000; j++){
try {
String text = Thread.currentThread().getName()+" 生产"+j;
queue.enqueue(text);
System.out.println(text);
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setName("生产线程"+i);
threads.add(t);
}
for(int i = 1; i<= 10; i++){
Thread t = new Thread(()->{
for(int j=0; j< 100000; j++){
try {
String result = queue.dequeue();
System.out.println(Thread.currentThread().getName()+" 消费 "+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setName("消费线程"+i);
threads.add(t);
}
for(Thread t : threads){
t.start();
}
for(Thread t : threads){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Counter {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
class BoundedQueue<T> {
private Queue<T> queue = new LinkedList<>();
private int capacity;
private ReentrantLock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BoundedQueue(int capacity) {
this.capacity = capacity;
}
public void enqueue(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await();
}
queue.add(item);
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public T dequeue() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
T item = queue.poll();
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
}
}