Static initializer and static synchronized method synchronization problem

I encountered a lock problem in my application, which contains several classes, as shown below:

public interface AppClient { void hello(); } public class Client implements AppClient { public synchronized static AppClient getInstance() { return instance; } public void hello() { System.out.println("Hello Client"); } private final static class InnerClient implements AppClient { public void hello() { System.out.println("Hello InnerClient"); } } private static AppClient instance; static { instance = new InnerClient(); doSomethingThatWillCallClientGetInstanceSeveralTimes(); } } public class Application { new Thread() { AppClient c = Client.getInstance(); c.hello(); }.start(); new Thread() { AppClient c = Client.getInstance(); c.hello(); }.start(); // ... new Thread() { AppClient c = Client.getInstance(); c.hello(); }.start(); } 

In the doSomethingThatWillCallClientGetInstanceSeveralTimes () method, it will perform quite a few initialization operations involving many classes and cyclically call the static Client.getInstance method several times during initialization (I understand that this is not good, but this is an outdated code base that lasts more than 20 years).

Here is my problem:

1) I thought that until the Client class was initialized, only the first thread that started the Client class initialization can access the Client.getInstance method, because the JVM will synchronize in the Client.class object until the class is initialized. I read JLS on the relevant topic and came to this conclusion (section 12.4.2, Detailed Initialization Procedure, http://java.sun.com/docs/books/jls/third_edition/html/execution.html ).

2) However, this was not the behavior that I saw in my real environment. For example, there are three threads that call Client.getInstance (), thread-1 starts the initialization of Client.class and calls the client.getInstance () method in doSomethingThatWillCallClientGetInstanceSeveralTimes () several times. And until the doSomethingThatWillCallClientGetInstanceSeveralTimes () method is completed, thread-2 receives a lock on the Client.class object (as possible, but it happened) and enters the Client.getInstance method (since this method is a static synchronized method), For some reason, thread-2 cannot return an "instance" (I think it waits for Client.class to complete its initialization). At the same time, thread-1 cannot continue, because you still need to call Client.getInstance in doSomethingThatWillCallClientGetInstanceSeveralTimes () and cannot get a lock because it belongs to thread-2. Threaddump tells me that thread-2 is in RUNNABLE state and thread-1 is in BLOCKED state, waiting for a lock belonging to thread-2.

I can only reproduce this behavior in the 64-bit Java 6u23 JVM on Windows and I cannot reproduce the 32-bit Java 6 JVM + Windows environment. Can someone tell me what I am missing here? Is this kind of code doomed to cause such a lock, if so, why? Is my understanding of JLS wrong for this part? Or is this a JVM problem? Any help is appreciated. Thanks.

+4
source share
2 answers

JLS 12.4.2 explicitly states in (6) that while the initializer is running, locks are released. Therefore, I think you see a valid execution path. You might be better

  • kills a static initializer and performs synchronized lazy initialization in an accessor
  • manually synchronize static initializer code
  public synchronized static AppClient getInstance () {
       synchronized (Client.class) {
           if (instance == null) {
             instance = new InnerClient ();
             doSomethingThatWillCallClientGetInstanceSeveralTimes ();
           }
           return instance;
       }
     }

EDIT

Even better - after re-reading this paragraph in the specification, it is even possible to completely remove the synchronization in your original example - the VM will take care of this.

EDIT

Sorry for the misuse - an even more detailed reading should show that in (2) the second thread cannot get a lock (after detecting the current initialization, it "waits").

+1
source

It seems like a mistake to me. Although one thread invokes a static block, no other thread should have access to it. The error may be that another thread may get a class lock before initialization completes. :(

I would suggest you structure your code, so you don't need such a lock at startup. That sounds pretty complicated. for example, in your example, the client does not need to extend the client, and the instance can be initialized in the declared line. I would consider the following structure.

 enum Client implements AppClient { INSTANCE; public void hello() { System.out.println("Hello Client"); } } 

You can make the client volatile or use delegation so that it does not display the fact of a change in state (or implementation)

+3
source

Source: https://habr.com/ru/post/1334466/


All Articles