在Socket编程中,死锁问题主要发生在使用线程同步时。如果两个线程各自持有对方需要的锁,并且同时等待对方释放锁,就会发生死锁。
常见的死锁场景:
- 两个线程使用锁定同一个资源,然后试图锁定对方持有的资源,导致互相等待。
- 两个线程在锁定多个资源时,锁定顺序不同,导致互相等待。
避免死锁的主要方法:
1、 避免无序加锁:
- 对多个资源进行加锁时,保证所有线程以同样的顺序加锁。
- 这样每个线程在试图加锁时,要么能成功加锁,要么发现资源已经被其他线程锁定。不存在互相等待的情况。
// 正确加锁顺序
synchronized(lockA) {
synchronized(lockB) {
// ...
}
}
// 错误加锁顺序
synchronized(lockB) {
synchronized(lockA) {
// ...
}
}
2、 使用定时锁:
- 在加锁失败后,线程睡眠一定时间再重试,给其他线程机会执行。
- 这样就避免了线程在 obtainLock 方法内无限等待,发生死锁的情况。
public void obtainLock() {
int count = 0;
while (!lock.tryLock()) { // 尝试加锁
if (count++ > MAX_COUNT) { // 超过最大重试次数
throw new RuntimeException("time out");
}
try {
Thread.sleep(50); // 重试前睡50毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 加锁成功逻辑
}
3、 避免资源嵌套加锁:
- 若一个线程已经持有资源A的锁,在加锁资源B前应先释放A的锁。
- 否则如果其他线程已经持有资源B的锁并等待资源A,将发生死锁。
// 正确,避免嵌套加锁
synchronized(A) {
//...
}
synchronized(B) {
//...
}
// 错误,资源B加锁前未释放资源A Lock
synchronized(A) {
synchronized(B) {
//...
}
}
综上,避免无序加锁,使用定时锁机制及避免资源嵌套加锁可以有效减少Socket编程中发生的死锁问题。