2

I take the code from that answer- https://stackoverflow.com/a/9286697/2674303

Reason why I've created current topic that I don't understand why that code leads to deadlock:

public class Lock implements Runnable {

    static {
        System.out.println(Thread.currentThread().getId() + "# Getting ready to greet the world");
        try {
            System.out.println(Thread.currentThread().getId() + "# before lock creation");
            Lock target = new Lock();
            System.out.println(Thread.currentThread().getId() + "# after lock creation");
            Thread t = new Thread(target);
            t.start();
            System.out.println(Thread.currentThread().getId() + "# awaiting thread finish");
            t.join();
            System.out.println(Thread.currentThread().getId() + "# static block finished");
        } catch (InterruptedException ex) {
            System.out.println("won't see me");
        }
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread() + "Hello World! ");
    }

    public void run() {
        System.out.println(Thread.currentThread().getId() + "# Started thread");
        Thread t = new Thread(new Lock());
        t.start();
    }
}

I tried to start it a lot of time and it always leads to deadlock.

output always the same:

1# Getting ready to greet the world
1# before lock creation
1# after lock creation
1# awaiting thread finish
13# Started thread

I tried to make initializer non-static and after it code became not to lead to deadlock. So I believe it is somehow related with static class initialization.
Could you explain it?

Answer

Thanks John Skeet for answer but to simplify things I removed code lines which prevented me to understand that example:

public class Lock implements Runnable {
    static {
        try {
            Thread thread = new Thread(new Lock());
            thread.start();
            thread.join();
        } catch (InterruptedException ex) {
            System.out.println("won't see me");
        }
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread() + "Hello World! ");
    }

    public void run() {
        new Lock();
    }
}

It leads to the deadlock too

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
  • @Turing85: No, the `new Lock()` is the `run` method and the static initializer - not in a constructor. There's no recursion there. (It would run out of memory due to too many threads, if it weren't for the deadlock, but it wouldn't be a stack overflow.) – Jon Skeet Jun 15 '19 at 15:15
  • @JonSkeet Oh [there is a recursion (although it throws an `OutOfMemoryException`](https://ideone.com/w9kBTK)). If `run()` is executed (which it is if the thread is started), then a new `Lock`-object is created, which - again - is started in a separate thread. – Turing85 Jun 15 '19 at 15:20
  • You should never do anything like that in a static initializer. They are essentially specially locked and run in unexpected situations. Besides it makes testing, error handling and debugging really hard. – eckes Jun 15 '19 at 15:32
  • @eckes it is obvious fact – gstackoverflow Jun 15 '19 at 15:34
  • And Running a separate thread and waiting for its termination is equivalent (for most purposes) with directly running it `new Lock().run();` – eckes Jun 15 '19 at 15:37
  • @Turing85: The fact that it starts in a separate thread makes it not recursive in the normal way - it's not like there's ever two calls to `run()` in the same stack, hence the lack of a stack overflow exception. I agree it's recursive in a different way though :) – Jon Skeet Jun 15 '19 at 15:39
  • I think the question would actually be simpler and clearer if your `run()` method just contained `Lock lock = new Lock();`. That would still deadlock, and would be simpler code to reason about. – Jon Skeet Jun 15 '19 at 15:40
  • @eckes it is just artificial example and it doesn't have any production analog – gstackoverflow Jun 15 '19 at 15:45
  • A class cannot be initialized as long as the static initializer is active. And as Long as a Class is not initialized it cannot instantiate Objects. So even of you remove all threads you still cannot do new Lock() on-site the static initializer of Lock. – eckes Jun 15 '19 at 15:52
  • @eckes I posted simplified example. Is there way to simplify this? – gstackoverflow Jun 15 '19 at 17:30
  • @eckes, just **Lock lock = new Lock();** inside static initializer doesn't lead to deadlock – gstackoverflow Jun 15 '19 at 17:37
  • Yes I noticed, the lock is actually reentrant. – eckes Jun 15 '19 at 17:42
  • @eckes Frankly speaking I don't catch how reentrancy affect your comment. Anyway I don't know non-reentrant locks in java besides **StampedLock** – gstackoverflow Jun 15 '19 at 17:46
  • @ecke sorry, I understood if initialization lock acquired initialization lock it can acquire it second time for new Lock – gstackoverflow Jun 15 '19 at 17:48
  • Yes, but I think it does skip the initialisation (and not Re-entner the class lock) if the current thread is the owner of the lock (that’s more consistent with the observation) – eckes Jun 15 '19 at 17:54
  • @eckes I agree, It is highly likely – gstackoverflow Jun 15 '19 at 18:07

1 Answers1

7

The new thread is trying to call run within the Lock class. That method itself tries to create a new instance of Lock. It can't do that1 until the Lock class has finished being initialized. The JVM knows that another thread is already initializing the class, so it blocks until that initialization is finished.

Unfortunately, that initialization can't complete until run completes, because of the t.join() call. So each of the two threads needs the other to make progress before it can do anything - deadlock.

It's definitely worth trying to avoid doing a lot of work in class initializers, for precisely this reason.

That would all happen even if the run() method itself were empty. But it's worse than that - because the run() method won't finish until it's created another thread and waited for that thread to complete calling run(), etc. So that's another cause of failure - it would basically spawn threads until the JVM ran out of resources. So even removing the type initializer won't get you to working code.

In terms of why type initialization is required, see section 12.4.1 of the JLS:

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.
  • A static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).

The expression new Lock() will always either initialization of Lock, or wait for initialization to complete (which may already have happened, of course).


1If you split the code in run so that you create the instance of Lock and then log, then start the thread, you'll see it's the creation of Lock that's blocking.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194