多线程的工作的程序总免不了讨论死锁的问题。死锁的发生需要4个条件:
1.互斥--一个资源同一时间只能被一个线程所使用
2.请求和保持--一个线程因请求资源而阻塞时,不会释放原先持有的资源。
3.不剥夺--一个线程不能夺取其它线程已抢占的资源
4.循环等待
既然已经知道了产生死锁的原因,我们就可以从这四点入手,得出避免死锁的几种方法:
1.不互斥。如果能设计成不互斥,那当然是最好的方案了。其次,可以使用线程安全的AtomicXXX类,比如用AtomicInteger代替Integer或int。使用AtomicXXX类并非高枕无忧,使用它,主要是使用它所提供的一些原子方法来避免使用锁。仅仅机械地用AtomicInteger代替Integer是不行的。
2.拒绝等待。比如使用ReentrantLock类的tryLock方法(C#中也有Monitor类的TryEnter方法),当一定时间内无法获得资源时就放弃。一种方案是,当tryLock失败时,释放自身所有资源,等待随机的时间后(让其它线程先完成任务并释放资源)重新尝试去获得资源。类似的方案一般都可能产生两个问题,一是线程饥饿(一个线程一直无法获得全部所需要资源),这样导致CPU利用率很低。二是活锁,上面的方案中采用了随机的等待时间来打乱顺序,避免活锁死循环。
3.允许资源抢占。这种方法设计比较复杂,只适用在特定的方案。
4.改变获取资源的顺序。这种方案是并不是按照需要资源的次序来请求资源,问题是导致某些资源被提前锁定。如以前课程中遇到一个实验,N个和尚(分打水的和喝水的),共有3个桶,1口井,还有一口能装10桶水的缸(其它题目要求略)。如果按需求来锁定资源,对于每个打水的和尚,顺序是
抢占1个桶-抢井-释放井-抢缸的一个空位-释放资源;
对于每个喝水的和尚,顺序是
抢桶-抢缸的一个满位-释放资源。
一种死锁的情况是三个喝水的和尚抢了三个桶,但是缸里没水,而打水的和尚因一直在等桶而无法继续。
我的解决方案是,对于每个打水的和尚,顺序是
抢缸的一个空位-抢1个桶-抢井-释放资源。
对于喝水的和尚,顺序是
抢缸的一个满位-抢1个桶-抢井-释放资源。
对于更简单一点的情况,可以使用强制固定顺序获得资源来避免死锁。如果每个线程都使用同一顺序获得资源,就不会死锁,如:
- synchronized(a)
- {
- synchronized(b)
- {
- //Thread1, Thread2
- }
- }
反之,如果某个线程采用相反顺序获得资源,就有可能死锁了
- synchronized(b)
- {
- synchronized(a)
- {
- //Thread3
- }
- }