目录
- 1、死锁
- 1、产生原因
- 2、死锁场景
- 3、解决方案
- 1. 顺序获取锁
- 2. 使用 tryLock (带超时)
- 3. Phaser
- 4. 银行家算法
- 2、活锁
- 1、介绍
- 2、场景
- 3、处理方案
- 1. 引入随机退避
- 2. 限制重试次数
- 3. 调整锁获取顺序
- 3、联系
- 总结
本章还是重点介绍下Java开发过程中,会出现的锁。
在多线程编程中,死锁(Deadlock)和活锁(Livelock)都是线程间协调不当导致的执行困境,但二者的表现和机制有显著区别。
并发编程中的常见问题
├── 活锁 (Livelock)
│ ├── 定义:两个或多个进程或线程相互之间不断尝试执行某个操作,但由于彼此的影响,这些操作都无法成功完成│ ├── 特点:系统不会崩溃,但也不会取得任何进展│ ├── 示例:线程A和线程B互相等待对方改变标志│├── 饥饿 (Starvation)│ ├── 定义:某个线程或进程由于资源竞争或调度策略的原因,长时间无法获得必要的资源,导致其无法执行│ ├── 特点:某些线程或进程始终得不到执行的机会│ ├── 示例:低优先级任务始终无法执行│├── 无锁 (Lock-Free)│ ├── 定义:不使用传统的互斥锁,而是使用原子操作和内存模型来实现线程安全│ ├── 特点:提高并发性能,减少锁的竞争│ ├── 示例:使用 AtomicInteger 实现线程安全的计数器│└── 死锁 (Deadlock) ├── 定义:两个或多个进程或线程在执行过程中,因争夺资源而造成的一种相互等待的现象 ├── 特点:系统陷入停滞状态,无法继续执行 ├── 示例:两个线程互相等待对方持有的锁
1、死锁
1、产生原因
满足下面四个必要条件,则会产生死锁现象。
1、互斥条件:
线程对所分配到的资源进行排他性使用,即在一段时间内某资源只由一个线程占用。如果此时还有其它线程请求该资源,则请求者只能等待,直至占有资源的线程用毕释放。
2、请求和保持条件:
线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其它线程占有,此时请求线程阻塞,但又对自己已获得的其它资源保持不放。
3、不剥夺条件:
线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4、循环等待条件:
在发生死锁时,必然存在一个线程 —— 资源的环形链,即线程集合 {T0,T1,T2,・・・,Tn} 中的 T0 正在等待一个 T1 占用的资源;T1 正在等待 T2 占用的资源,……,Tn 正在等待已被 T0 占用的资源。
代码示例:
class DeadlockExample { private static final Object resource1 = new Object(); private static final Object resource2 = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { android synchronized (resource1) { System.out.println("Thread 1: Holding resource 1..."); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread 1: Waiting for resource 2..."); synchronized (resource2) { System.out.println("Thread 1: Holding resource 1 and 2..."); } } }); Thread thread2 = new Thread(() -> { synchronized (resource2) { System.out.println("Thread 2: Holding resource 2..."); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread 2: Waiting for resource 1..."); synchronized (resource1) { System.out.println("Thread 2: Holding resource 1 and 2..."); } } }); thread1.start(); thread2.start(); } }
代码解释
resource1
和resource2
是两个共享资源。thread1
先获取resource1
,然后尝试获取resource2
。thread2
先获取resource2
,然后尝试获取resource1
。- 由于
thread1
和thread2
都在等待对方释放资源,从而导致死锁。
由上面可知,死锁的四个条件均满足。
2、死锁场景
3、解决方案
3.1. 预防
预防死锁:
通过破坏产生死锁的四个必要条件中的一个或几个来预防死锁的发生。例如,一次性获取所有需要的资源,避免请求和保持条件;或者允许资源被剥夺。
避免死锁:
在资源分配过程中,通过算法来判断是否会发生死锁,只有在不会发生死锁的情况下才进行资源分配。例如,银行家算法。
1. 顺序获取锁
方案:确保所有线程以相同的顺序获取锁,避免不同的锁获取顺序(最有效方案)。
public class DeadlockSolution1 { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread1 acquired lock1"); // 添加小延迟,增加死锁概率 try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock2) { System.out.println("Thread1 acquired lock2"); } } }); Thread thread2 = new Thread(() -> { synchronized (lock1) { // 改为先获取lock1,与thread1顺序一致 System.out.println("Thread2 acquired lock1"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock2) { System.out.println("Thread2 acquired lock2"); } } }); thread1.start(); thread2.start(); } }
2. 使用 tryLock (带超时)
方案:使用 ReentrantLock
的 tryLock()
方法,避免无限期等待,可以增加回退方案,并且超时情况下线程会释放掉锁。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class DeadlockSolution2 { private static final Lock lock1 = new ReentrantLock(); private static final Lock lock2 = new ReentrantLock(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { try { if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) { System.out.println("Thread1 acquired lock1"); try { Thread.sleep(100); } catch (InterruptedException e) {} if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) { System.out.println("Thread1 acquired lock2"); lock2.unlock(); } else { System.out.println("Thread1 failed to acquire lock2"); } lock1.unlock(); } else { System.out.println("Thread1 failed to acquire lock1"); } } catch (InterruptedException e) { e.printStackTrace(); } }); Thread thread2 = new Thread(() -> { try { if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) { System.out.println("Thread2 acquired lock2"); try { Thread.编程客栈sleep(100); } catch (InterruptedException e) {} 编程客栈 if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) { System.out.println("Thread2 acquired lock1"); lock1.unlock(); } else { System.out.println("Thread2 failed to acquire lock1"); } lock2.unlock(); } else { System.out.println("Thread2 failed to acquire lock2"); } } catch (InterruptedException e) { e.printStackTrace(); } }); thread1.start(); thread2.start(); } }
3. Phaser
方案:使用 java.util.concurrent
包中的高级工具类替代显式锁。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Phaser; public class DeadlockSolution4 { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); Phaser phaser = new Phaser(2); // 使用Phaser协调线程 executor.submit(() -> { System.out.println("Task1 started"); phaser.arriveAndAwaitAdvance(); // 等待其他线程 System.out.println("Task1 completed"); }); executor.submit(() -> { System.out.println("Task2 started"); phaser.arriveAndAwaitAdvance(); // 等待其他线程 System.out.println("Task2 completed"); }); executor.shutdown(); } }
4. 银行家算法
- 安全状态:系统能按某种顺序为所有进程分配资源,使它们都能顺利完成
- 不安全状态:系统可能进入死锁的状态
四种数据结构:
- Available:可用资源向量
- Max:每个进程最大需求矩阵
- Allocation:已分配资源矩阵
- Need:需求矩阵(Need = Max - Allocation)
实现步骤:
1. 安全状态检查算法
boolean isSafeState() { // 1. 初始化 int[] work = Arrays.copyOf(Available, Available.length); boolean[] finish = new boolean[processCount]; Arrays.fill(finish, false); // 2. 寻找可满足的进程 int count = 0; while (count < processCount) { boolean found = false; for (int i = 0; i < processCount; i++) { if (!finish[i] && checkNeedLessThanWork(i, work)) { // 3. 假设进程i释放资源 for (int j = 0; j < resourceTypes; j++) { work[j] += Allocation[i][j]; } finish[i] = true; found = true; count++; } } if (!found) break; // 没有找到可满足的进程 } // 4. 检查是否所有进程都能完成 return count == processCount; } boolean checkNeedLessThanWork(int process, int[] work) { for (int i = 0; i < resourceTypes; i++) { if (Need[process][i] > work[i]) { return false; } } return true; }
2. 资源请求算法
boolean requestResources(int process, int[] request) { // 1. 检查请求是否超过声明需求 for (int i = 0; i < resourceTypes; i++) { if (request[i] > Need[process][i]) { return false; } } // 2. 检查系统是否有足够资源 for (int i = 0; i < resourceTypes; i++) { if (request[i] > Available[i]) { return false; } } // 3. 尝试分配 for (int i = 0; i < resourceTypes; i++) { Available[i] -= request[i]; Allocation[process][i] += request[i]; Need[process][i] -= request[i]; } // 4. 检查安全性 if (isSafeState()) { return true; } else { // 回滚分配 for (int i = 0; i < resourceTypes; i++) { Available[i] += request[i]; Allocation[process][i] -= request[i]; Need[process][i] += request[i]; } return false; } }
银行家算法通过以下机制预防死锁:
事前预防:在资源分配前进行安全性检查
- 只有当分配后系统仍处于安全状态时才允许分配
- 避免了系统进入可能导致死锁的状态
破坏死锁必要条件:
- 破坏"循环等待"条件:通过确保至少有一个进程总能获得所需资源
- 破坏"占有并等待"条件:通过要求进程一次性声明最大需求
动态决策:
- 每次资源请求都重新评估系统安全性
- 比静态预防策略更灵活
3.2. 检测
检测死锁:
- 通过一定的算法来检测系统中是否存在死锁。
- 例如,资源分配图算法。
解除死锁:
- 当检测到死锁后,需要采取措施来解除死锁。
- 例如,剥夺某个线程的资源,或者终止某个线程。
2、活锁
1、介绍
活锁是指线程不断改变状态来响应对方,但整体任务无法推进。
线程未阻塞,仍在不断尝试执行操作,但因相互干扰陷入无效循环,CPU 利用率不为 0(线程在执行无意义的重试)。
2、场景
- 两个线程(
Worker1
和Worker2
)需要同时获取两把锁(lock1
和lock2
)才能工作。 - 如果某个线程发现锁被占用,它会主动释放自己的锁并重试(而不是阻塞等待)。
- 由于两个线程同时让步,导致它们无限循环地释放和重试,但永远无法完成任务。
线程的调度由于不停切换,也会导致。
代码示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LivelockExample { private final Lock lock1 = new ReentrantLock(); private final Lock lock2 = new ReentrantLock(); public void worker1() { while (true) { lock1.lock(); System.out.println("Worker1 获取 lock1"); // 尝试获取 lock2,如果失败就释放 lock1 并重试 if (lock2.tryLock()) { System.out.println("Worker1 获取 lock2,完成任务"); lock2.unlock(); lock1.unlock(); break; } else { System.out.println("Worker1 无法获取 lock2,释放 lock1 并重试"); lock1.unlock(); } // 短暂休眠,避免 CPU 100% try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } www.devze.com } } public void worker2() { while (true) { lock2.lock(); System.out.println("Worker2 获取 lock2"); // 尝试获取 lock1,如果失败就释放 lock2 并重试 if (lock1.tryLock()) { System.out.println("Worker2 获取 lock1,完成任务"); lock1.unlock(); lock2.unlock(); break; } else { System.out.println("Worker2 无法获取 lock1,释放 lock2 并重试"); lock2.unlock(); } // 短暂休眠,避免 CPU 100% try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) { LivelockExample example = new LivelockExample(); Thread t1 = new Thread(example::worker1); Thread t2 = new Thread(example::worker2); t1.start(); t2.start(); } }
以下是可能的执行顺序:
Worker1 获取 lock1
Worker2 获取 lock2Worker1 无法获取 lock2,释放 lock1 并重试Worker2 无法获取 lock1,释放 lock2 并重试Worker1 获取 lock1Worker2 获取 lock2Worker1 无法获取 lock2,释放 lock1 并重试Worker2 无法获取 lock1,释放 lock2 并重试...(无限循环)
现象:
- 两个线程都在运行(没有阻塞,不像死锁)。
- 它们不断尝试获取对方的锁,但发现锁被占用后主动释放自己的锁并重试。
- 由于同时让步,导致无限循环,任务无法完成。
3、处理方案
1. 引入随机退避
示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.Random; public class RandomBackoffSolution { private final Lock lock1 = new ReentrantLock(); private final Lock lock2 = new ReentrantLock(); private final Random random = new Random(); public void worker1() { while (true) { lock1.lock(); System.out.println("Worker1 获取 lock1"); try { // 引入随机退避 Thread.sleep(random.nextInt(100)); // 随机等待0-99ms if (lock2.tryLock()) { try { System.out.println("Worker1 获取 lock2,完成任务"); return; // 成功完成任务 } finally { lock2.unlock(); } } else { System.out.println("Worker1 无法获取 lock2,释放 lock1 并重试"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock1.unlock(); } // 再次随机退避 try { Thread.sleep(random.nextInt(100)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public void worker2() { while (true) { lock2.lock(); System.out.println("Worker2 获取 lock2"); try { // 引入随机退避 Thread.sleep(random.nextInt(100)); // 随机等待0-99ms if (lock1.tryLock()) { try { System.out.println("Worker2 获取 lock1,完成任务"); return; // 成功完成任务 } finally { lock1.unlock(); } } else { System.out.println("Worker2 无法获取 lock1,释放 lock2 并重试"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock2.unlock(); } // 再次随机退避 try { Thread.sleep(random.nextInt(100)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) { RandomBackoffSolution solution = new RandomBackoffSolution(); new Thread(solution::worker1).start(); new Thread(solution::worker2).start(); } }
2. 限制重试次数
示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class RetryLimitSolution { private final Lock lock1 = new ReentrantLock(); private final Lock lock2 = new ReentrantLock(); private static final int MAX_RETRIES = 5; public void worker1() { int retryCount = 0; while (retryCount < MAX_RETRIES) { retryCount++; lock1.lock(); System.out.println("Worker1 获取 lock1 (尝试 " + retryCount + "/" + MAX_RETRIES + ")"); try { if (lock2.tryLock()) { try { System.out.println("Worker1 获取 lock2,完成任务"); return; } finally { lock2.unlock(); } } else { System.out.println("Worker1 无法获取 lock2,释放 lock1 并重试"); } } finally { lock1.unlock(); } try { Thread.sleep(10www.devze.com0); // 固定等待时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.println("Worker1 达到最大重试次数,放弃任务"); } public void worker2() { int retryCount = 0; while (retryCount < MAX_RETRIES) { retryCount++; lock2.lock(); System.out.println("Worker2 获取 lock2 (尝试 " + retryCount + "/" + MAX_RETRIES + ")"); try { if (lock1.tryLock()) { try { System.out.println("Worker2 获取 lock1,完成任务"); return; } finally { lock1.unlock(); } } else { System.out.println("Worker2 无法获取 lock1,释放 lock2 并重试"); } } finally { lock2.unlock(); } try { Thread.sleep(100); // 固定等待时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.println("Worker2 达到最大重试次数,放弃任务"); } public static void main(String[] args) { RetryLimitSolution solution = new RetryLimitSolution(); new Thread(solution::worker1).start(); new Thread(solution::worker2).start(); } }
3. 调整锁获取顺序
这个方案比较推荐。示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class OrderedLockSolution { private final Lock lock1 = new ReentrantLock(); private final Lock lock2 = new ReentrantLock(); // 定义全局锁获取顺序 private static final Object lockOrder = new Object(); public void worker1() { while (true) { // 按照固定顺序获取锁 synchronized (lockOrder) { lock1.lock(); System.out.println("Worker1 获取 lock1"); try { if (lock2.tryLock()) { try { System.out.println("Worker1 获取 lock2,完成任务"); return; } finally { lock2.unlock(); } } else { System.out.println("Worker1 无法获取 lock2,释放 lock1 并重试"); } } finally { lock1.unlock(); } } try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public void worker2() { while (true) { // 按照相同顺序获取锁 synchronized (lockOrder) { lock1.lock(); System.out.println("Worker2 获取 lock1"); try { if (lock2.tryLock()) { try { System.out.println("Worker2 获取 lock2,完成任务"); return; } finally { lock2.unlock(); } } else { System.out.println("Worker2 无法获取 lock2,释放 lock1 并重试"); } } finally { lock1.unlock(); } } try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) { OrderedLockSolution solution = new OrderedLockSolution(); new Thread(solution::worker1).start(); new Thread(solution::worker2).start(); } }
3、联系
总结
理解死锁和活锁的区别与联系,有助于开发出更健壮的并发程序。在实际开发中,应当优先考虑使用java.util.concurrent
包提供的高级并发工具,而非直接使用低级的同步机制。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
精彩评论