如何避免 Socket 编程中的死锁问题?

在Socket编程中,死锁问题主要发生在使用线程同步时。如果两个线程各自持有对方需要的锁,并且同时等待对方释放锁,就会发生死锁。

常见的死锁场景:

  1. 两个线程使用锁定同一个资源,然后试图锁定对方持有的资源,导致互相等待。
  2. 两个线程在锁定多个资源时,锁定顺序不同,导致互相等待。

避免死锁的主要方法:
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编程中发生的死锁问题。