在多线程编程中,线程的死锁和并发安全是两个重要的概念。理解这两个概念并正确地管理它们,对于编写高效且可靠的并发程序至关重要。
线程的死锁
死锁(Deadlock) 是指两个或多个线程相互等待对方释放已经持有的资源,导致它们无法继续执行的现象。死锁会导致程序卡住,无法继续执行。
死锁的四个必要条件
- 互斥条件:一个资源一次只能被一个线程占用。
- 持有并等待条件:一个线程已经持有至少一个资源,但又申请新的资源,而该资源被其他线程持有。
- 不剥夺条件:线程已获得的资源在未使用完之前,不能被其他线程强行剥夺,只能由持有该资源的线程自行释放。
- 环路等待条件:若干线程之间形成一种头尾相接的环形等待资源关系。
示例代码
以下代码演示了一个简单的死锁情况:
public class DeadlockExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public static void main(String[] args) {
DeadlockExample example = new DeadlockExample();
Thread thread1 = new Thread(example::method1);
Thread thread2 = new Thread(example::method2);
thread1.start();
thread2.start();
}
public void method1() {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock 1...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
public void method2() {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock 2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (lock1) {
System.out.println("Thread 2: Holding lock 2 & 1...");
}
}
}
}
在这个示例中,thread1
持有 lock1
并等待 lock2
,同时 thread2
持有 lock2
并等待 lock1
,这就导致了死锁。
预防死锁的方法
- 避免嵌套锁:尽量减少持有多个锁的情况。
- 按顺序获取锁:所有线程按照相同的顺序获取锁。
- 使用尝试锁:使用
tryLock
方法尝试获取锁,如果无法获取就放弃。 - 锁超时:设置锁的超时时间,避免无限等待。
并发安全
并发安全(Concurrency Safety) 是指在多线程环境下,正确地管理对共享资源的访问,避免竞争条件(Race Conditions)和数据不一致性。
竞争条件
竞争条件是指多个线程同时访问和修改共享资源时,由于访问顺序的不确定性,导致程序行为异常。
并发安全的实现
-
synchronized:内置锁机制,确保同一时间只有一个线程可以执行同步代码块或方法。
public synchronized void synchronizedMethod() { // Critical section } public void synchronizedBlock() { synchronized (this) { // Critical section } }
-
Lock:显式锁机制,比
synchronized
更灵活。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private final Lock lock = new ReentrantLock(); public void lockMethod() { lock.lock(); try { // Critical section } finally { lock.unlock(); } } }
-
volatile:保证变量的可见性,即一个线程修改了
volatile
变量的值,其他线程可以立即看到这个变化。public class VolatileExample { private volatile boolean flag = true; public void setFlag(boolean flag) { this.flag = flag; } public boolean getFlag() { return flag; } }
-
Atomic Classes:使用
java.util.concurrent.atomic
包提供的原子类,确保原子操作。import java.util.concurrent.atomic.AtomicInteger; public class AtomicExample { private final AtomicInteger counter = new AtomicInteger(0); public void increment() { counter.incrementAndGet(); } public int getValue() { return counter.get(); } }
-
ReadWriteLock:用于区分读锁和写锁,允许多个线程同时读取,但写操作是独占的。
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockExample { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private int data; public void writeData(int newData) { readWriteLock.writeLock().lock(); try { data = newData; } finally { readWriteLock.writeLock().unlock(); } } public int readData() { readWriteLock.readLock().lock(); try { return data; } finally { readWriteLock.readLock().unlock(); } } }
总结
- 死锁:线程相互等待对方释放资源,导致程序卡住。预防方法包括避免嵌套锁、按顺序获取锁、使用尝试锁和锁超时。
- 并发安全:确保多个线程正确地访问共享资源,避免竞争条件和数据不一致。常用工具包括
synchronized
、Lock
、volatile
、原子类和ReadWriteLock
。
通过理解和正确使用这些工具,可以编写高效、安全的多线程程序。