Deadlock Analysis in NetBeans 8
- by Geertjan
Lock contention profiling is very important in multi-core environments. Lock contention occurs when a thread tries to acquire a lock while another thread is holding it, forcing it to wait. Lock contentions result in deadlocks. Multi-core environments have even more threads to deal with, causing an increased likelihood of lock contentions. In NetBeans 8, the NetBeans Profiler has new support for displaying detailed information about lock contention, i.e., the relationship between the threads that are locked. After all, whenever there's a deadlock, in any aspect of interaction, e.g., a political deadlock, it helps to be able to point to the responsible party or, at least, the order in which events happened resulting in the deadlock.
As an example, let's take the handy Deadlock sample code from the Java Tutorial and look at the tools in NetBeans IDE for identifying and analyzing the code. The description of the deadlock is nice:
Alphonse and Gaston are friends, and great believers in courtesy. A strict rule of courtesy is that when you bow to a friend, you must remain bowed until your friend has a chance to return the bow. Unfortunately, this rule does not account for the possibility that two friends might bow to each other at the same time.
To help identify who bowed first or, at least, the order in which bowing took place, right-click the file and choose "Profile File". In the Profile Task Manager, make the choices below:
When you have clicked Run, the Threads window shows the two threads are blocked, i.e., the red "Monitor" lines tell you that the related threads are blocked while trying to enter a synchronized method or block:
But which thread is holding the lock? Which one is blocked by the other? The above visualization does not answer these questions.
New in NetBeans 8 is that you can analyze the deadlock in the new Lock Contention window to determine which of the threads is responsible for the lock:
Here is the code that simulates the lock, very slightly tweaked at the end, where I use "setName" on the threads, so that it's even easier to analyze the threads in the relevant NetBeans tools. Also, I converted the anonymous inner Runnables to lambda expressions.
package org.demo;
public class Deadlock {
static class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s"
+ " has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s"
+ " has bowed back to me!%n",
this.name, bower.getName());
}
}
public static void main(String[] args) {
final Friend alphonse
= new Friend("Alphonse");
final Friend gaston
= new Friend("Gaston");
Thread t1 = new Thread(() -> {
alphonse.bow(gaston);
});
t1.setName("Alphonse bows to Gaston");
t1.start();
Thread t2 = new Thread(() -> {
gaston.bow(alphonse);
});
t2.setName("Gaston bows to Alphonse");
t2.start();
}
}
In the above code, it's extremely likely that both threads will block when they attempt to invoke bowBack. Neither block will ever end, because each thread is waiting for the other to exit bow.
Note: As you can see, it really helps to use "Thread.setName", everywhere, wherever you're creating a Thread in your code, since the tools in the IDE become a lot more meaningful when you've defined the name of the thread because otherwise the Profiler will be forced to use thread names like "thread-5" and "thread-6", i.e., based on the order of the threads, which is kind of meaningless. (Normally, except in a simple demo scenario like the above, you're not starting the threads in the same class, so you have no idea at all what "thread-5" and "thread-6" mean because you don't know the order in which the threads were started.) Slightly more compact:
Thread t1 = new Thread(() -> {
alphonse.bow(gaston);
},"Alphonse bows to Gaston");
t1.start();
Thread t2 = new Thread(() -> {
gaston.bow(alphonse);
},"Gaston bows to Alphonse");
t2.start();