下面我来详细讲解“Java并发编程之浅谈ReentrantLock”的完整攻略。
一、ReentrantLock概述
在Java中,我们可以用synchronized
来实现线程同步。除此之外,还可以使用JDK提供的ReentrantLock
类来实现线程同步和互斥。
ReentrantLock
是一个可重入锁,它和synchronized
相比,具有更加灵活的特性。使用ReentrantLock
需要手动加锁和释放锁,需要注意的是在加锁和释放锁的过程中,一定要遵循一定的规则,否则会出现死锁和无法释放锁等问题。
二、ReentrantLock的基本用法
1. 创建ReentrantLock对象
ReentrantLock lock = new ReentrantLock();
2. 加锁和释放锁
在需要同步的代码块前后加上lock.lock()
和lock.unlock()
即可。
lock.lock(); // 加锁
try {
// 同步代码块
} finally {
lock.unlock(); // 释放锁
}
3. ReentrantLock的可重入性
ReentrantLock
是可重入锁,即同一个线程在拥有锁的情况下,可以再次加锁,而不会发生死锁。下面是一个示例:
class Test {
private ReentrantLock lock = new ReentrantLock();
public void doSomething() {
lock.lock();
try {
System.out.println("do something");
doSub();
} finally {
lock.unlock();
}
}
private void doSub() {
lock.lock();
try {
System.out.println("do sub");
} finally {
lock.unlock();
}
}
}
在上述代码中,doSub()
方法也调用了lock
的lock()
方法来加锁,但因为是同一个线程,在doSub()
中成功获取了锁,执行完毕后再次释放锁,不会发生死锁。
4. ReentrantLock的公平性和非公平性
ReentrantLock
可以设置为公平锁或非公平锁。公平锁的意思是,线程在加锁时需要排队,按照加锁的顺序来获取锁;非公平锁的意思是,线程在加锁时不需要排队,可以直接尝试获取锁。
ReentrantLock lock = new ReentrantLock(true); // 公平锁
ReentrantLock lock = new ReentrantLock(false); // 非公平锁
需要注意的是,公平锁会降低程序的并发度,因为线程需要排队等待锁的释放;而非公平锁可以提高程序的并发度,但可能会导致一些线程长时间获取不到锁。
5. ReentrantLock的可中断性
ReentrantLock
可以设置成可中断锁,即允许在等待锁的过程中,被其他线程中断,方式和synchronized
不同的是,需要在加锁的时候使用lock.lockInterruptibly()
方法。
try {
lock.lockInterruptibly(); // 可中断锁
} catch (InterruptedException e) {
e.printStackTrace();
}
6. ReentrantLock的超时性
ReentrantLock
也可以设置超时时间,在等待锁的过程中,如果在规定时间内没有获取到锁,就放弃等待。
if (lock.tryLock(timeout, TimeUnit.SECONDS)) {
// 成功获取到锁,在规定时间内获取到了锁
try {
// 同步代码块
} finally {
lock.unlock();
}
} else {
// 在规定时间内未获取到锁,执行其他操作
}
三、示例说明
示例一:使用ReentrantLock实现线程安全的计数器
class Counter {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
在上述代码中,increment()
方法使用ReentrantLock
进行加锁,保证了线程安全;getCount()
方法没有使用同步锁,因为在没有修改操作的情况下,多线程并发读操作是安全的。
示例二:使用ReentrantLock实现生产者-消费者模型
class ProducerConsumer {
private List<Integer> list = new ArrayList<>();
private ReentrantLock lock = new ReentrantLock();
private Condition notFull = lock.newCondition(); // 等待“非满”条件
private Condition notEmpty = lock.newCondition(); // 等待“非空”条件
public void produce(int n) {
lock.lock();
try {
while (list.size() >= 10) {
notFull.await();
}
list.add(n);
System.out.println(Thread.currentThread().getName() + "生产了" + n + ",当前库存为" + list.size());
notEmpty.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
while (list.size() <= 0) {
notEmpty.await();
}
int n = list.remove(0);
System.out.println(Thread.currentThread().getName() + "消费了" + n + ",当前库存为" + list.size());
notFull.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
在上述代码中,我们先定义了一个list
用来存放数据,然后使用ReentrantLock
和Condition
实现了生产者和消费者的线程安全操作。
需要注意的是,在生产者中我们使用了notFull.await()
来等待“非满”条件,在消费者中我们使用了notEmpty.await()
等待“非空”条件。当list
已经满了时,生产者会进入等待状态,等待消费者取走一些数据;当list
为空时,消费者会进入等待状态,等待生产者生产一些数据。同时,使用signalAll()
来通知等待线程,当有新的数据生产或消费时,就会唤醒对应的线程。我们使用signalAll()
而不是signal()
来避免出现虚假唤醒的情况。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java并发编程之浅谈ReentrantLock - Python技术站