一、什么是重入锁?
重入锁是一种支持在同一线程中反复获取同一把锁的锁。在使用重入锁之前,需要先获取锁,然后才能执行加锁前后的逻辑,最后再释放锁。
与 synchronized 关键字相比,重入锁具有可重入性,synchronized 是不可重入的,同一线程加锁会阻塞自己。重入锁相比 synchronized,性能更高,可以方便的实现公平锁和非公平锁,同时也支持条件变量等高级功能。
二、重入锁的使用攻略
1. 创建重入锁
在 Java 中,使用 java.util.concurrent.locks.ReentrantLock 类创建重入锁。
Lock lock = new ReentrantLock();
2. 获取锁
我们可以通过 lock() 方法获取锁,如果当前锁已被其他线程占用,则当前线程会被阻塞,直到其获得锁为止。
lock.lock();
try {
// 执行加锁前的逻辑
} finally {
lock.unlock();
}
3. 可重入性
对于同一个线程,可以多次获取同一个锁,而不会被阻塞。
lock.lock();
try {
// 执行加锁前的逻辑
lock.lock();
try {
// 执行加锁前的逻辑
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
4. 公平锁和非公平锁
在创建重入锁时,可以选择创建公平锁或是非公平锁。公平锁会按照线程加锁的顺序进行获取锁,而非公平锁则不保证获取锁的顺序。
// 创建公平锁
Lock fairLock = new ReentrantLock(true);
// 创建非公平锁
Lock unfairLock = new ReentrantLock(false);
5. 条件变量
与 synchronized 相比,重入锁还支持条件变量的特性。条件变量可以让一些线程在满足某些条件时才唤醒,从而改善线程的竞争情况。
Condition condition = lock.newCondition();
lock.lock();
try {
while (!conditionSatisfied) {
condition.await();
}
// 执行加锁前的逻辑
} catch (InterruptedException e) {
// 处理异常情况
} finally {
lock.unlock();
}
lock.lock();
try {
conditionSatisfied = true;
condition.signalAll();
} finally {
lock.unlock();
}
三、重入锁的示例
1. 示例一:使用重入锁实现安全的计数器
在不使用锁的情况下,多个线程同时对计数器进行自增操作时,会造成计数器值的不确定性。而使用重入锁可以保证对计数器的操作是安全的。
public class Counter {
private int count;
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();
}
}
}
2. 示例二:使用重入锁实现读写锁
读写锁是一种特殊的锁,它允许多个线程同时读取共享数据,但只允许一个线程写入共享数据。
我们可以通过重入锁和 Condition 变量实现读写锁。
public class ReadWriteLock {
private int readers;
private int writers;
private int writeRequests;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void lockRead() throws InterruptedException {
lock.lock();
try {
while (writers > 0 || writeRequests > 0) {
condition.await();
}
readers++;
} finally {
lock.unlock();
}
}
public void unlockRead() {
lock.lock();
try {
readers--;
if (readers == 0) {
condition.signalAll();
}
} finally {
lock.unlock();
}
}
public void lockWrite() throws InterruptedException {
lock.lock();
try {
writeRequests++;
while (readers > 0 || writers > 0) {
condition.await();
}
writeRequests--;
writers++;
} finally {
lock.unlock();
}
}
public void unlockWrite() {
lock.lock();
try {
writers--;
condition.signalAll();
} finally {
lock.unlock();
}
}
}
四、总结
以上就是重入锁的完整使用攻略。在实际开发过程中,重入锁的应用是比较常见的,其具有可重入性和高性能等多重优点,可以避免死锁等问题的出现。但是需要注意的是,使用重入锁时需要注意锁的粒度,不要出现占用锁时间过长的情况,以免影响性能。