死锁
死锁(Deadlock)描述的是这样一种情况:多个进程/线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于进程/线程被无限期地阻塞,因此程序不可能正常终止。
必要条件
- 互斥条件:每个资源只能由一个进程占用。其他进程想要获取该资源时,需要阻塞等待。
- 持有并等待条件:一个进程已经持有至少一个资源,并且正在等待获取一个被其他进程占有的资源。
- 不可抢占条件:资源不能被强制性地从一个进程中剥夺,只能由持有它的进程显式地释放。
- 环路等待条件:存在一个进程集 {P1, P2, …, Pn},其中 P1 等待 P2 占有的资源,P2 等待 P3 占有的资源,…,Pn 等待 P1 占有的资源,形成一个环。
解决方法
预防
预防死锁的关键是破坏上述四个条件中的一个。
破坏 互斥条件:使得资源是可以同时访问的,这是种简单的方法,但很显然有很多资源往往是不能同时访问的 ,会存在安全风险,所以这种做法在大多数的场合是行不通的。
破坏持有并等待条件:在进程开始时一次性请求所有资源,或者在进程请求资源时,不占有任何资源。但很进程运行过程中资源消耗时间可能相隔很久,而这段时间资源却始终无法被其他进程使用,这导致资源利用率下降。
破坏 不可抢占条件:也就是剥夺式调度算法,但剥夺式调度方法可能导致进程间互相抢夺资源,会导致资源利用率下降。
破坏环路等待条件:对资源进行全局排序,进程必须按照顺序请求资源,防止形成环。
避免
通过资源分配算法来避免死锁,其中最著名的是银行家算法(Banker’s Algorithm),它在资源分配之前检查系统状态,确保不会进入不安全状态。算法的步骤如下:
- 初始化:输入总资源数量、各进程的最大需求和当前分配的资源,计算每个进程的剩余需求和系统的可用资源。
- 资源请求:当一个进程请求资源时,首先检查请求是否小于或等于其最大需求以及系统的可用资源。
- 试探性分配:如果资源请求合理,试探性地分配资源,即暂时将资源分配给进程,并更新相应的数据结构(可用资源、已分配资源和剩余需求)。
- 安全性检查:调用安全性算法,检查系统是否处于安全状态。如果系统仍然处于安全状态,正式分配资源;否则,回滚试探性分配。
- 恢复:如果系统不安全,撤销试探性分配,并通知进程等待资源。
检测
对资源的分配加以限制可以预防和避免死锁的发生,但是都不利于各进程对系统资源的充分共享。
死锁的检测与解除对资源的分配不加以任何限制,也不采取死锁避免措施,但系统定时地运行一个 “死锁检测” 的程序,判断系统内是否出现死锁,如果检测到系统发生了死锁,再采取措施去解除它。
解除
一旦检测到死锁,可以通过以下方法解除:
- 终止进程:终止部分或所有陷入死锁的进程。
- 资源剥夺:强制性从部分进程中剥夺资源,并将这些资源分配给其他进程,以解除死锁。
代码模拟死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class deadLock { 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 get Lock1"); try{ Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("Thread1 waiting for Lock2"); synchronized (lock2){ System.out.println("Thread1 get Lock2"); } } }); Thread thread2 = new Thread(() -> { synchronized (lock2){ System.out.println("Thread2 get Lock2"); try{ Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("Thread2 waiting for Lock1"); synchronized (lock1){ System.out.println("Thread2 get Lock1"); } } }); thread1.start(); thread2.start(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package main
import ( "fmt" "sync" "time" )
func main() { var lock1, lock2 sync.Mutex
go func() { lock1.Lock() fmt.Println("Goroutine 1: locked lock1")
time.Sleep(1 * time.Second)
fmt.Println("Goroutine 1: attempting to lock lock2") lock2.Lock() fmt.Println("Goroutine 1: locked lock2")
lock2.Unlock() lock1.Unlock() }()
go func() { lock2.Lock() fmt.Println("Goroutine 2: locked lock2")
time.Sleep(1 * time.Second)
fmt.Println("Goroutine 2: attempting to lock lock1") lock1.Lock() fmt.Println("Goroutine 2: locked lock1")
lock1.Unlock() lock2.Unlock() }()
time.Sleep(3 * time.Second) fmt.Println("Main: finished") }
|