2011-01-06 4 views
4

나는 다음과 같은 몇 가지 클래스가 포함되어있는 내 응용 프로그램에서 일부 잠금 문제로 실행 다음 doSomethingThatWillCallClientGetInstanceSeveralTimes() 메소드에서정적 초기화 및 정적 동기화 방법 잠금 문제

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(); 
} 

, 꽤 많은 초기화를 할 것입니다 많은 클래스를 포함하는 작업을 초기화하는 동안 Client.getInstance 정적 메서드를 여러 번 순환 호출합니다. 그러나 이것은 좋지 않습니다. 그러나 이것은 20 년 이상 지속되는 레거시 코드 기반입니다. 내가 클래스 클라이언트 초기화, JVM은 Client.class 객체에 동기화하기 때문에 Client.getInstance 방법에 액세스 할 수있는 수준의 클라이언트 초기화를 트리거 첫 번째 스레드를 완료하기 전에 생각

1) : 여기

내 문제입니다 클래스 초기화가 완료되기 전에 관련 주제에 관한 JLS를 읽고이 결론에 도달했습니다 (12.4.2 절, 자세한 초기화 절차, http://java.sun.com/docs/books/jls/third_edition/html/execution.html).

2) 그러나 실제 환경에서 보았던 것처럼 행동하지 않았습니다. 예를 들어, Client.getInstance()를 호출하는 세 개의 스레드가 있으며, thread-1은 Client.class 초기화를 트리거하고 doSomethingThatWillCallClientGetInstanceSeveralTimes() 메소드에서 Client.getInstance()를 여러 번 호출합니다. 그리고 doSomethingThatWillCallClientGetInstanceSeveralTimes() 메소드가 완료되기 전에 thread-2는 Client.class 객체의 잠금을 얻습니다 (어떻게 가능합니까?). Client.getInstance 메소드로 들어갑니다 (이 메소드는 정적 동기화 메소드이기 때문에) . 어떤 이유로 Thread-2는 "인스턴스"를 반환 할 수 없습니다 (Client.class가 초기화를 완료하기를 기다리고있는 것 같습니다). 동시에 thread-1은 doSomethingThatWillCallClientGetInstanceSeveralTimes()에서 Client.getInstance를 호출해야하기 때문에 계속 진행할 수 없으며 thread-2가 소유하고 있으므로 잠금을 획득 할 수 없습니다. Threaddump는 thread-2가 RUNNABLE 상태이고 thread-1이 thread-2가 소유 한 잠금을 기다리는 BLOCKED 상태에 있다고 알려줍니다.

Windows의 64 비트 Java 6u23 JVM에서만이 동작을 재현 할 수 있으며 32 비트 Java 6 JVM + Windows 환경을 재현 할 수 없습니다. 누군가 내가 뭘 놓치고 있는지 말해 줄 수 있니? 이러한 종류의 코드가 그러한 잠금을 야기 할 운명에 처해 있습니까? 그렇다면 어떻게해야합니까? 이 부분에 대한 JLS에 대한 이해가 잘못 되었습니까? 아니면 JVM 문제입니까? 어떤 도움을 주셔서 감사합니다. 감사.

+0

코드가 다소 혼란 스럽습니다. 특히 Client와 InnerClient가 AppClient를 구현할 때 특히 그렇습니다. 클라이언트가 필요한 이유를 알 수 없습니다. 문제를 보여주는 짧지만 완전한 * 프로그램을 만들 수 있다면 많은 도움이 될 것입니다. –

+0

@ 존이 문제를 재현하는 간단한 프로그램을 만들 수는 없습니다. 위의 프로그램은 커다란 라이브러리에서 추출한 해골 코드입니다. 문제가 있다고 생각했는데, Client와 InnerClient가 모두 AppClient를 구현하여 역 비교가 가능하기 때문에 중요한 정보를 놓치고 싶지는 않습니다. 이 문제의 원인이되어야합니다. 어쩌면 간단한 프로그램으로이 문제를 보여주기 위해 더 정확한 타이밍과 행운이 필요할 것입니다. 어쨌든 목록에있는 현재 프로그램이 동작 1)처럼 동작 할 것입니다. – nybon

+0

음, 나는 제어 된 방식으로 재생산하는 것에 집중할 것입니다 - 그게 없으면 우리는 정말로 추측 할 수 있기 때문입니다. 당신이 말했듯이, 그것은 * 작동해야만하는 것처럼 들립니다 ... –

답변

1

JLS 12.4 .2는 (6)에서 을 명시 적으로 나타내고, 초기화 프로그램을 실행하면 loc ks가 릴리스됩니다. 그래서 나는 당신이 유효한 실행 경로를 보았다고 생각합니다.당신은 정적 초기화를 죽이는

  • 의 더 나은 및 접근 동기화 게으른 초기화를 할 수
  • 수동으로 정적 초기화 코드
 
    public synchronized static AppClient getInstance() { 
     synchronized(Client.class) { 
      if (instance == null) { 
      instance = new InnerClient(); 
      doSomethingThatWillCallClientGetInstanceSeveralTimes(); 
      } 
      return instance; 
     } 
    } 

편집

더 나은 동기화 - 후를 사양 에서이 단락을 다시 읽고, 심지어 완전히 을 제거 할 수 있습니다 동기화를 제거 원래 예제에서는 VM이 ​​처리합니다.

편집 misguiding에 대한

죄송합니다 - (계속 초기화, 그것을 "대기"를 감지 한 후) (2), 두 번째 스레드가 잠금을 획득 할 수있는 것을 공개해야 더 자세한 독서.

+0

고마워. 나는 JLS에서 그 점을 포착하지 않았다 :(그러나, 나는 아직도 이것에 대해 약간의 질문이있다 .Client.class 초기화가 완료되기 전에 초기화 스레드를 제외하고, 다른 스레드가 Client.class 잠금을 획득하는 것이 가능하다고 생각 하는가? Client.getInstance() synchronized static method? JLS는 어떤 클래스의 정적 메소드를 호출하기 전에 클래스 초기화가 필요해 보이는 것 같습니다 . – nybon

+0

맞습니다. (2 회) (2)에 따라 다시 한 번 읽고 다시 읽어야합니다. 그 순간에 그는 ** 당신이 getInstance 주위에 두었던 동기화에 도달 할 수 없어야한다. (나는 여전히 그것을 제거하는 것이 안전하다고 생각한다) . 내 32b 1.6_22와 디버깅 세션에서 그런 상태에 도달하지 못했지만, 64b 1.6_23도 똑같이 괜찮습니다. – mtraut

+0

수동 동기화는 실제 환경에서 내 문제를 해결해주었습니다. 이것을 이해하지 못한다. 완전히 떠들다. 나중에 Mac에서이 문제를 재현하려고 노력할 것입니다. 어쨌든, 당신의 대답은 제게 많은 도움이됩니다. 감사. – nybon

3

나에게는 버그처럼 보입니다. 한 스레드가 정적 블록을 호출하는 동안 다른 스레드가 해당 스레드에 액세스 할 수 없어야합니다. 버그는 초기화가 완료되기 전에 다른 스레드가 클래스에 대한 잠금을 획득 할 수있는 것일 수 있습니다. :

시동시 잠금이 필요하지 않도록 코드를 구성하는 것이 좋습니다. 예를 들어 클라이언트가 클라이언트를 확장 할 필요가없고 인스턴스가 라인의은에 선언했다. 나는 다음과 같은 구조를 고려할 것입니다.

enum Client implements AppClient { 
    INSTANCE; 

    public void hello() { 
     System.out.println("Hello Client"); 
    } 
} 

당신은 클라이언트 변경 가능하거나 그것이 상태 (또는 이행)을 변경할 수 있습니다 사실을 노출하지 않도록 위임을 사용할 수 있습니다

+0

"초기화가 완료되기 전에 다른 스레드가 클래스 잠금을 획득 할 수있는 버그 일 수 있습니다." JLS 섹션 12.4.2, 상세 초기화 절차를 읽은 후에는 이것이 가능하지 않다고 생각했습니다. 불행히도 이전 버전과 호환되지 않으므로이 라이브러리에 의존하는 코드가 중단되므로 사용자가 제안한 변경 작업을 수행 할 수 없습니다. – nybon

+0

나는 Peter에게 이것에 동의 할 것이다. 나는 스펙을 읽었으며 12.4.2에서 "자바 가상 머신의 구현은 다음 절차를 사용하여 동기화 및 재귀 초기화를 담당한다"라고 언급했다. 특정 버전의 Java에서 이러한 현상이 발생하면 JDK 버그 목록에 파일을 보관해야합니다. – Vivek

+0

이 동작은 사양을 준수합니다 (아래 답변 참조). ** ** 초기화 프로그램이 실행되는 동안 VM에서는 잠금을 유지하지 않습니다. – mtraut