(Whether using static initializers in Java is a good idea is out of scope for this question.)
I am encountering deadlocks in my Scala application, which I think are caused by interlocking static initializers in the compiled classes.
My question is how to detect and diagnose these deadlocks -- I have found that the normal JVM tools for deadlocks do not seem to work when static initializer blocks are involved.
Here is a simple example Java app which deadlocks in a static initializer:
public class StaticDeadlockExample implements Runnable
{
static
{
Thread thread = new Thread(
new StaticDeadlockExample(),
"StaticDeadlockExample child thread");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args)
{
System.out.println("in main");
}
public static void sayHello()
{
System.out.println("hello from thread " + Thread.currentThread().getName());
}
@Override
public void run() {
StaticDeadlockExample.sayHello();
}
}
If you launch this app, it deadlocks. The stack trace at time of deadlock (from jstack
) contains the following two deadlocked threads:
"StaticDeadlockExample child thread" prio=6 tid=0x000000006c86a000 nid=0x4f54 in Object.wait() [0x000000006d38f000]
java.lang.Thread.State: RUNNABLE
at StaticDeadlockExample.run(StaticDeadlockExample.java:37)
at java.lang.Thread.run(Thread.java:619)
Locked ownable synchronizers:
- None
"main" prio=6 tid=0x00000000005db000 nid=0x2fbc in Object.wait() [0x000000000254e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000004a6a7870> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1143)
- locked <0x000000004a6a7870> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1196)
at StaticDeadlockExample.<clinit>(StaticDeadlockExample.java:17)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:169)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:116)
Locked ownable synchronizers:
- None
My questions are as follows
For starters, you can detect a deadlock in Java by examining the application's thread dump. You can even use a command to generate the thread dump with some operating systems.
Avoid Nested Locks: A deadlock mainly happens when we give locks to multiple threads. Avoid giving a lock to multiple threads if we already have given to one. Avoid Unnecessary Locks: We can have a lock only those members which are required. Having a lock unnecessarily can lead to a deadlock.
If your resources are A, B, and C, then all threads should acquire them in the order of A -> B -> C, or A -> C, or B -> C, etc. Deadlock can occur if one thread acquires them in the order A -> B -> C while another thread acquires them in the order C -> B -> A.
Scala makes it easy to fall into the trap.
The easy workaround or diagnostic (if you see clinit in your stack trace) is to let your object extend App to let DelayedInit take your code off the static initializer.
Some clarifying links:
https://issues.scala-lang.org/browse/SI-7646
Scala: Parallel collection in object initializer causes a program to hang
http://permalink.gmane.org/gmane.comp.lang.scala.user/72499
I have tried this example with my tool and it also fails in detecting this as a deadlock. After struggling a bit with the jconsole debugger and re-running the example a couple of times I noticed that the initial thread is marked as RUNNABLE because it is runnable, the problem here is that since the launched thread access to a static member, this operation is queued after the finishing of the static initializer block (this semantic is not clear in the JVM specification, however it seems to be the case).
The static initializer does not finish because in this bizarre example a join operation forces it to wait for the thread termination, however I remark that this "queued" operation does not grab a lock explicitly nor implicitly according to the JVM specification. Perhaps this shouldn't be considered a deadlock per se as it would be the same case if the body of the run method contains an infinite loop.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With