This article is translated by Importnew-rookie_sam from Dzone. Welcome to join the translation team. Reproduced please see at the end of the request.
Deadlock means that two or more actions are waiting for other actions to complete and all actions are always in a blocked state. It is very difficult to detect deadlocks during the development phase, and it is often necessary to restart the program to unlock the deadlock. To make things worse, deadlocks usually occur in the most heavily loaded production process, and it is not easy to find them in the test. The reason for this is that all possible intersections between test threads are unrealistic. Although there are some static analysis libraries that can help us discover the possible deadlocks, we still need to detect deadlocks at runtime and get useful information to solve this problem or to restart the program, or to do something else.
Using the Threadmxbean class to detect deadlocks in programming
Java 5 introduces the Threadmxbean interface, which provides a variety of methods for monitoring threads. I recommend that you understand all of these methods, because when you are not using external tools, they provide you with many useful actions to monitor program performance. Here, we are interested in the method is Findmonitordeadlockedthreads, if you are using Java 6, the corresponding method is finddeadlockedthreads. The difference between the two is that the finddeadlockedthreads can also detect the owner Locks (java.util.concurrent) causes a deadlock, and findmonitordeadlockedthreads can only detect monitor locks (for example, a sync block). Because the method of preserving the old version is only for compatibility reasons, I will use the new version of the method. Here, the idea of programming is to encapsulate the periodic detection of deadlocks into a reusable component, and then we just have to start it and go with it.
One way to implement scheduling is through an executor framework, a set of well-abstracted and easy-to-use multithreaded classes.
12 |
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( 1 ); this .scheduler.scheduleAtFixedRate(deadlockCheck, period, period, unit); |
It is that simple, after we set a specific time by selecting a period and a time unit, we get a thread that is periodically called. Next, we want to extend the functionality to allow the user to provide the behavior that triggers when the program detects a deadlock. Finally, we need a way to receive a series of objects that describe all the threads in the deadlock.
1 |
void handleDeadlock( final ThreadInfo[] deadlockedThreads); |
Now, implementing the deadlock detection class is ready.
123456789101112131415161718192021222324252627282930313233343536373839 |
public interface DeadlockHandler {
void handleDeadlock(
final ThreadInfo[] deadlockedThreads);
}
public class DeadlockDetector {
private final DeadlockHandler deadlockHandler;
private final long period;
private final TimeUnit unit;
private final ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(
1
);
final Runnable deadlockCheck =
new Runnable() {
@Override
public void run() {
long
[] deadlockedThreadIds = DeadlockDetector.
this
.mbean.findDeadlockedThreads();
if (deadlockedThreadIds !=
null
) {
ThreadInfo[] threadInfos =
DeadlockDetector.
this
.mbean.getThreadInfo(deadlockedThreadIds);
DeadlockDetector.
this
.deadlockHandler.handleDeadlock(threadInfos);
}
}
};
public DeadlockDetector(
final DeadlockHandler deadlockHandler,
final long period,
final TimeUnit unit) {
this
.deadlockHandler = deadlockHandler;
this
.period = period;
this
.unit = unit;
}
public void start() {
this
.scheduler.scheduleAtFixedRate(
this
.deadlockCheck,
this
.period,
this
.period,
this
.unit);
}
}
|
Let's try it. First, we will create a handler used to output the deadlock thread to the System.err. In a real-world scenario, we can use it to send messages, such as:
123456789101112131415161718192021222324252627 |
public class DeadlockConsoleHandler
implements DeadlockHandler {
@Override
public void handleDeadlock(
final ThreadInfo[] deadlockedThreads) {
if (deadlockedThreads !=
null
) {
System.err.println(
"Deadlock detected!"
);
Map<Thread, StackTraceElement[]> stackTraceMap = Thread.getAllStackTraces();
for (ThreadInfo threadInfo : deadlockedThreads) {
if (threadInfo !=
null
) {
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread.getId() == threadInfo.getThreadId()) {
System.err.println(threadInfo.toString().trim());
for (StackTraceElement ste : thread.getStackTrace()) {
System.err.println(
"t" + ste.toString().trim());
}
}
}
}
}
}
}
}
|
This process is repeated in all stack traces and the corresponding stack traces are printed for each thread information. In this way, we can know exactly where each thread waits and what the object is. But there is a flaw in this approach-when a thread waits only temporarily, it may be treated as a temporary deadlock, triggering an erroneous alert. For this reason, when we handle deadlocks, the original thread cannot continue to exist and the Finddeadlockedthreads method returns no such thread. In order to avoid possible nullpointerexception, we need to be wary of this situation. Finally, let's get a deadlock to see how the system works.
123456789101112131415161718192021222324252627282930313233343536 |
DeadlockDetector deadlockDetector =
new DeadlockDetector(
new DeadlockConsoleHandler(),
5
, TimeUnit.SECONDS);
deadlockDetector.start(); final Object lock1 =
new Object();
final Object lock2 =
new Object();
Thread thread1 =
new Thread(
new Runnable() {
@Override
public void run() {
synchronized (lock1) {
System.out.println(
"Thread1 acquired lock1"
);
try {
TimeUnit.MILLISECONDS.sleep(
500
);
}
catch (InterruptedException ignore) {
}
synchronized (lock2) {
System.out.println(
"Thread1 acquired lock2"
);
}
}
}
});
thread1.start();
Thread thread2 =
new Thread(
new Runnable() {
@Override
public void run() {
synchronized (lock2) {
System.out.println(
"Thread2 acquired lock2"
);
synchronized (lock1) {
System.out.println(
"Thread2 acquired lock1"
);
}
}
}
});
thread2.start();
|
Output:
123456789 |
Thread1 acquired lock1
Thread2 acquired lock2
Deadlock detected!
“Thread-
1
” Id=
11 BLOCKED on java.lang.Object
@68ab95e6 owned by “Thread-
0
” Id=
10
deadlock.DeadlockTester$
2
.run(DeadlockTester.java:
42
)
java.lang.Thread.run(Thread.java:
662
)
“Thread-
0
” Id=
10 BLOCKED on java.lang.Object
@58fe64b9 owned by “Thread-
1
” Id=
11
deadlock.DeadlockTester$
1
.run(DeadlockTester.java:
28
)
java.lang.Thread.run(Thread.java:
662
)
|
Remember, the overhead of deadlock detection can be very large, you need to use your program to test whether you really need deadlock detection and how often to detect. I recommend a deadlock detection interval of at least a few minutes, because more frequent detection doesn't make much sense, because we don't have a recovery plan, and all we can do is debug and handle errors or restart programs and pray that deadlocks won't happen again. If you have any good suggestions or questions about solving a deadlock problem, please leave a comment below.
Original link: Dzone translation: Importnew.com-rookie_sam
Link: http://www.importnew.com/15307.html
How to programmatically discover Java deadlocks