1. Overview
1.概述
While multi-threading helps in improving an application’s performance, it also comes with some problems. In this tutorial, we’ll look into two such problems, deadlock and livelock, with the help of Java examples.
虽然多线程有助于提高应用程序的性能,但它也伴随着一些问题。在本教程中,我们将借助Java实例来研究这样两个问题:deadlock 和 livelock。
2. Deadlock
2.僵局
2.1. What Is Deadlock?
2.1.什么是死锁?
A deadlock occurs when two or more threads wait forever for a lock or resource held by another of the threads. Consequently, an application may stall or fail as the deadlocked threads cannot progress.
当两个或多个线程永远等待另一个线程持有的锁或资源时,就会出现死锁。因此,应用程序可能会停滞或失败,因为死锁的线程无法取得进展。
The classic dining philosophers problem nicely demonstrates the synchronization issues in a multi-threaded environment and is often used as an example of deadlock.
经典的dining philosophers问题很好地展示了多线程环境下的同步问题,并经常被用作死锁的例子。
2.2. Deadlock Example
2.2.死锁实例
First, let’s take a look into a simple Java example to understand deadlock.
首先,让我们看一下一个简单的Java例子来理解死锁。
In this example, we’ll create two threads, T1 and T2. Thread T1 calls operation1, and thread T2 calls operations.
在这个例子中,我们将创建两个线程,T1和T2。 线程T1调用operation1,而线程T2调用operations。
To complete their operations, thread T1 needs to acquire lock1 first and then lock2, whereas thread T2 needs to acquire lock2 first and then lock1. So, basically, both the threads are trying to acquire the locks in the opposite order.
为了完成他们的操作,线程T1需要先获取lock1,然后再获取lock2,而线程T2需要先获取lock2,然后再获取lock1。所以,基本上,两个线程都试图以相反的顺序获取锁。
Now, let’s write the DeadlockExample class:
现在,让我们来编写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
}
Let’s now run this deadlock example and notice the output:
现在让我们运行这个死锁的例子并注意输出。
Thread T1: lock1 acquired, waiting to acquire lock2.
Thread T2: lock2 acquired, waiting to acquire lock1.
Once we run the program, we can see that the program results in a deadlock and never exits. The log shows that thread T1 is waiting for lock2, which is held by thread T2. Similarly, thread T2 is waiting for lock1, which is held by thread T1.
一旦我们运行该程序,我们可以看到,该程序导致了一个死锁,并且从未退出。日志显示,线程T1正在等待lock2,该线程由T2持有。同样,线程T2正在等待lock1,该线程由T1持有。
2.3. Avoiding Deadlock
2.3.避免死锁
Deadlock is a common concurrency problem in Java. Therefore, we should design a Java application to avoid any potential deadlock conditions.
死锁是Java中一个常见的并发问题。因此,我们应该设计一个Java应用程序以避免任何潜在的死锁情况。
To start with, we should avoid the need for acquiring multiple locks for a thread. However, if a thread does need multiple locks, we should make sure that each thread acquires the locks in the same order, to avoid any cyclic dependency in lock acquisition.
首先,我们应该避免为一个线程获取多个锁的需要。然而,如果一个线程确实需要多个锁,我们应该确保每个线程以相同的顺序获取锁,以避免锁获取中的任何循环依赖。
We can also use timed lock attempts, like the tryLock method in the Lock interface, to make sure that a thread does not block infinitely if it is unable to acquire a lock.
我们还可以使用定时的锁尝试,比如Lock接口中的tryLock方法,以确保线程在无法获得锁时不会无限地阻塞。
3. Livelock
3.锁定
3.1. What Is Livelock
3.1.什么是Livelock
Livelock is another concurrency problem and is similar to deadlock. In livelock, two or more threads keep on transferring states between one another instead of waiting infinitely as we saw in the deadlock example. Consequently, the threads are not able to perform their respective tasks.
活锁是另一个并发性问题,与死锁类似。在死锁中,两个或多个线程不断地在彼此之间转移状态,而不是像我们在死锁例子中看到的那样无限地等待。因此,这些线程无法执行各自的任务。
A great example of livelock is a messaging system where, when an exception occurs, the message consumer rolls back the transaction and puts the message back to the head of the queue. Then the same message is repeatedly read from the queue, only to cause another exception and be put back on the queue. The consumer will never pick up any other message from the queue.
一个很好的livelock的例子是一个消息传递系统,当异常发生时,消息消费者回滚交易,并把消息放回队列的头部。然后,同一消息被重复地从队列中读取,只是为了引起另一个异常,并被放回队列中。消费者永远不会从队列中拿起任何其他消息。
3.2. Livelock Example
3.2.活锁实例
Now, to demonstrate the livelock condition, we’ll take the same deadlock example we’ve discussed earlier. In this example also, thread T1 calls operation1 and thread T2 calls operation2. However, we’ll change the logic of these operations slightly.
现在,为了证明活锁条件,我们将采取我们先前讨论过的同样的死锁例子。在这个例子中,线程T1调用操作1,线程T2调用操作2。然而,我们将稍微改变这些操作的逻辑。
Both threads need two locks to complete their work. Each thread acquires its first lock but finds that the second lock is not available. So, in order to let the other thread complete first, each thread releases its first lock and tries to acquire both the locks again.
两个线程都需要两个锁来完成他们的工作。每个线程都获得了它的第一个锁,但发现第二个锁是不可用的。因此,为了让另一个线程先完成工作,每个线程都释放其第一个锁,并试图再次获得两个锁。
Let’s demonstrate livelock with a LivelockExample class:
让我们用一个LivelockExample类来演示livelock。
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
}
Now, let’s run this example:
现在,我们来运行这个例子。
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.
..
As we can see in the logs, both the threads are repeatedly acquiring and releasing locks. Because of this, none of the threads are able to complete the operation.
正如我们在日志中看到的,两个线程都在重复地获取和释放锁。正因为如此,没有一个线程能够完成这个操作。
3.3. Avoiding Livelock
3.3.避开活锁
To avoid a livelock, we need to look into the condition that is causing the livelock and then come up with a solution accordingly.
为了避免活锁,我们需要研究导致活锁的条件,然后提出相应的解决方案。
For example, if we have two threads that are repeatedly acquiring and releasing locks, resulting in livelock, we can design the code so that the threads retry acquiring the locks at random intervals. This will give the threads a fair chance to acquire the locks they need.
例如,如果我们有两个线程反复获取和释放锁,导致livelock,我们可以设计代码,使线程以随机的时间间隔重新尝试获取锁。这将给线程一个公平的机会来获取他们需要的锁。
Another way to take care of the liveness problem in the messaging system example we’ve discussed earlier is to put failed messages in a separate queue for further processing instead of putting them back in the same queue again.
在我们前面讨论过的消息系统的例子中,另一种解决有效性问题的方法是将失败的消息放在单独的队列中进行进一步处理,而不是将它们再次放回同一个队列中。
4. Conclusion
4.总结
In this tutorial, we’ve discussed deadlock and livelock. Also, we’ve looked into Java examples to demonstrate each of these problems and briefly touched upon how we can avoid them.
在本教程中,我们已经讨论了死锁和活锁。此外,我们还研究了Java的例子来证明这些问题的每一个,并简要地谈到了我们如何避免它们。
As always, the complete code used in this example can be found over on GitHub.
像往常一样,本例中使用的完整代码可以在GitHub上找到。