在Java并发编程中,互斥锁(Mutex Lock)可以用来保护多个资源,以确保同一时刻只有一个线程能够访问这些资源。但是,需要注意的是,如果多个资源共享同一把锁,那么在某些情况下可能会引入新的问题,比如死锁。下面我将介绍如何使用一把锁来保护多个资源,并讨论一些潜在的问题以及如何避免这些问题。
1. 使用一把锁保护多个资源
假设我们有两个共享资源resourceA
和resourceB
,并且我们想要使用一把锁来保护对这两个资源的操作。下面是一个简单的示例:
import java.util.concurrent.locks.ReentrantLock;
public class MultiResourceLock {
private final ReentrantLock lock = new ReentrantLock();
private int resourceA = 0;
private int resourceB = 0;
public void incrementResourceA() {
lock.lock();
try {
resourceA++;
} finally {
lock.unlock();
}
}
public void incrementResourceB() {
lock.lock();
try {
resourceB++;
} finally {
lock.unlock();
}
}
public int getResourceA() {
lock.lock();
try {
return resourceA;
} finally {
lock.unlock();
}
}
public int getResourceB() {
lock.lock();
try {
return resourceB;
} finally {
lock.unlock();
}
}
}
public class MultiResourceLockDemo {
public static void main(String[] args) {
final MultiResourceLock manager = new MultiResourceLock();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
manager.incrementResourceA();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
manager.incrementResourceB();
}
});
thread1.start();
thread2.start();
// 等待所有线程完成
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Resource A: " + manager.getResourceA());
System.out.println("Final Resource B: " + manager.getResourceB());
}
}
在这个示例中,我们定义了一个MultiResourceLock
类,它包含两个资源resourceA
和resourceB
,以及一个ReentrantLock
实例。每个资源的读写方法都使用相同的锁来保护。
2. 潜在问题:死锁
尽管使用一把锁可以确保对多个资源的访问是原子性的,但是在某些情况下可能会导致死锁问题。例如,如果有两个线程分别持有不同的锁,并试图获取对方持有的锁,那么这两个线程将无限期地等待下去,导致死锁。
示例代码
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockExample {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
private int resourceA = 0;
private int resourceB = 0;
public void incrementBothResources() {
// 死锁风险
lockA.lock();
lockB.lock();
try {
resourceA++;
resourceB++;
} finally {
lockA.unlock();
lockB.unlock();
}
}
public void incrementResourceA() {
lockA.lock();
try {
resourceA++;
} finally {
lock.unlock();
}
}
public void incrementResourceB() {
lockB.lock();
try {
resourceB++;
} finally {
lockB.unlock();
}
}
public int getResourceA() {
lockA.lock();
try {
return resourceA;
} finally {
lockA.unlock();
}
}
public int getResourceB() {
lockB.lock();
try {
return resourceB;
} finally {
lockB.unlock();
}
}
}
public class DeadlockDemo {
public static void main(String[] args) {
final DeadlockExample manager = new DeadlockExample();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
manager.incrementResourceA();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
manager.incrementResourceB();
}
});
Thread thread3 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
manager.incrementBothResources();
}
});
thread1.start();
thread2.start();
thread3.start();
// 等待所有线程完成
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Resource A: " + manager.getResourceA());
System.out.println("Final Resource B: " + manager.getResourceB());
}
}
在这个示例中,我们增加了incrementBothResources()
方法,该方法试图同时修改resourceA
和resourceB
。由于两个资源分别由不同的锁保护,如果线程以不同的顺序获取锁,就有可能发生死锁。
3. 如何避免死锁
为了避免死锁,可以采取以下几种策略:
- 锁顺序:始终按照固定的顺序获取锁。例如,如果
lockA
总是先于lockB
获取,那么就不会发生死锁。 - 锁所有权:如果一个线程已经持有了
lockA
,那么它应该总是优先尝试获取lockB
,而不是相反。 - 锁超时:使用带超时的
tryLock()
方法尝试获取锁,如果无法立即获取锁,则放弃并释放已经持有的锁。 - 使用
Lock
接口的lockInterruptibly()
方法:如果线程被中断,那么锁会被自动释放。
示例代码:使用锁顺序避免死锁
import java.util.concurrent.locks.ReentrantLock;
public class AvoidDeadlockExample {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
private int resourceA = 0;
private int resourceB = 0;
public void incrementBothResources() {
// 按照固定的顺序获取锁
lockA.lock();
lockB.lock();
try {
resourceA++;
resourceB++;
} finally {
lockA.unlock();
lockB.unlock();
}
}
public void incrementResourceA() {
lockA.lock();
try {
resourceA++;
} finally {
lockA.unlock();
}
}
public void incrementResourceB() {
lockB.lock();
try {
resourceB++;
} finally {
lockB.unlock();
}
}
public int getResourceA() {
lockA.lock();
try {
return resourceA;
} finally {
lockA.unlock();
}
}
public int getResourceB() {
lockB.lock();
try {
return resourceB;
} finally {
lockB.unlock();
}
}
}
public class AvoidDeadlockDemo {
public static void main(String[] args) {
final AvoidDeadlockExample manager = new AvoidDeadlockExample();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
manager.incrementResourceA();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
manager.incrementResourceB();
}
});
Thread thread3 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
manager.incrementBothResources();
}
});
thread1.start();
thread2.start();
thread3.start();
// 等待所有线程完成
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Resource A: " + manager.getResourceA());
System.out.println("Final Resource B: " + manager.getResourceB());
}
}
在这个修改后的示例中,我们始终按照固定的顺序(lockA
先于lockB
)获取锁,从而避免了死锁的发生。
总结
使用一把锁保护多个资源可以确保这些资源的原子性访问,但在使用多把锁时需要小心处理以避免死锁问题。通过确保锁的获取顺序一致,可以有效地防止死锁的发生。
如果你有任何疑问或需要进一步的解释,请随时提问!