当多线程帮助咱们提升应用性能的同时,它同时也带来一些问题,本文咱们将借助几个小例子看下两个问题,死锁和活锁。java
死锁发生在当两个或多个线程一直在等待另外一个线程持有的锁或资源的时候。这会致使一个程序可能会被拖垮或者直接挂掉,由于线程们都不能继续工做了。多线程
经典的哲学家进餐
问题很是好的展现了多线程下的同步问题而且常常被用来看成死锁的例子。并发
首先,咱们看一个简单的Java例子来理解死锁。性能
在这个例子中,咱们建立两个线程,T1和T2。线程T1调用operation1,线程T2调用operation2。测试
为了完成操做,线程T1须要先获取到lock1再获取到lock2,而后此时线程T2须要先获取到lock2再获取到lock1。所以两个线程都在以相反的顺序获取锁。ui
如今,咱们写一下DeadlockExample:线程
public class DeadlockExample { private Lock lock1 = new ReentrantLock(true); private Lock lock2 = new ReentrantLock(true); public static void main(String[] args) { DeadlockExample deadlock = new DeadlockExample(); new Thread(deadlock::operation1, "T1").start(); new Thread(deadlock::operation2, "T2").start(); } public void operation1() { lock1.lock(); print("lock1 acquired, waiting to acquire lock2."); sleep(50); lock2.lock(); print("lock2 acquired"); print("executing first operation."); lock2.unlock(); lock1.unlock(); } public void operation2() { lock2.lock(); print("lock2 acquired, waiting to acquire lock1."); sleep(50); lock1.lock(); print("lock1 acquired"); print("executing second operation."); lock1.unlock(); lock2.unlock(); } // helper methods }
咱们运行一下这个例子看下输出:设计
Thread T1: lock1 acquired, waiting to acquire lock2. Thread T2: lock2 acquired, waiting to acquire lock1.
一运行这个例子咱们就能看到程序致使了一个死锁且永远也退出不了。输出日志展现了线程T1在等待lock2,但lock2被线程T2所持有。类似的,线程T2在等待lock1,他被T1所持有。日志
死锁在Java中是个很常见的并发问题,由于咱们应该设计一个程序来避免潜在的死锁条件。code
避免获取锁时的循环依赖问题
。tryLock
方法,来确保一个线程若是获取不到锁不会一直阻塞。活锁是另外一个并发问题,它和死锁很类似。在活锁中,两个或多个线程彼此间一直在转移状态,而不像咱们上个例子中互相等待。结果就是全部线程都不能执行它们各自的任务。
一个比较好的活锁例子就是消息队列。当发生异常的时候,消息消费者回滚事务并把消息放到队列头中,而后相同的消息又从队列头中被读到,又会形成异常并再次放入到队列头中。如此循坏往复,消费者永远读不到队列中其余的消息。
如今咱们展现一下活锁的状况,咱们一样拿上面死锁的例子来解释。线程T1调用operation1,线程T2调用operation2,可是咱们稍微改变的操做的逻辑。
两个线程都须要拿到两把锁来完成工做,每一个线程拿到第一个锁后都会发现拿不到第二把锁,所以为了让另外一个线程先完成任务,每一个线程都会释放第一把锁并会尝试再次获取到两把锁。
咱们来看下下面的测试例子
public class LivelockExample { private Lock lock1 = new ReentrantLock(true); private Lock lock2 = new ReentrantLock(true); public static void main(String[] args) { LivelockExample livelock = new LivelockExample(); new Thread(livelock::operation1, "T1").start(); new Thread(livelock::operation2, "T2").start(); } public void operation1() { while (true) { tryLock(lock1, 50); print("lock1 acquired, trying to acquire lock2."); sleep(50); if (tryLock(lock2)) { print("lock2 acquired."); } else { print("cannot acquire lock2, releasing lock1."); lock1.unlock(); continue; } print("executing first operation."); break; } lock2.unlock(); lock1.unlock(); } public void operation2() { while (true) { tryLock(lock2, 50); print("lock2 acquired, trying to acquire lock1."); sleep(50); if (tryLock(lock1)) { print("lock1 acquired."); } else { print("cannot acquire lock1, releasing lock2."); lock2.unlock(); continue; } print("executing second operation."); break; } lock1.unlock(); lock2.unlock(); } // helper methods }
咱们看下运行结果:
Thread T1: lock1 acquired, trying to acquire lock2. Thread T2: lock2 acquired, trying to acquire lock1. Thread T1: cannot acquire lock2, releasing lock1. Thread T2: cannot acquire lock1, releasing lock2. Thread T2: lock2 acquired, trying to acquire lock1. Thread T1: lock1 acquired, trying to acquire lock2. Thread T1: cannot acquire lock2, releasing lock1. Thread T1: lock1 acquired, trying to acquire lock2. Thread T2: cannot acquire lock1, releasing lock2.
能看到输出结果里,两个线程都在重复的获取锁和释放锁,致使两个线程都不能完成操做。
避免活锁咱们得观察一下活锁发生的条件并根据状况提出方案,好比: