2010-06-09 4 views
2

이것은 java에서 공유 객체를 동기화하는 올바른 방법에 관한 질문입니다. 한 가지주의 할 점은 공유하려는 개체에 정적 메서드에서 액세스해야한다는 것입니다. 내 질문은, 정적 필드에 동기화하는 경우 동기화 된 정적 메서드와 비슷한 방식으로 필드가 속한 클래스를 잠급니까? 아니면 필드 자체 만 잠글 것입니까?Java에서 공유 된 정적 객체를 동기화하는 올바른 방법은 무엇입니까?

필자의 구체적인 예에서는 PayloadService.getPayload() 또는 PayloadService.setPayload()를 호출하면 PayloadService.payload가 잠길 것인가? 아니면 전체 PayloadService 클래스를 잠글 것인가?

이 방법이 올바른지/수용 가능한 방법입니까?

예를 들어, PayloadService는 별도의 스레드이며 일정한 간격으로 페이로드 개체를 업데이트합니다. 다른 스레드는 PayloadService.getPayload()를 임의의 간격으로 호출하여 최신 데이터를 가져와야합니다.

public class PayloadHolder { 

private static PayloadHolder holder;  
private static PayloadDTO payload; 

private PayloadHolder(){   
} 

public static synchronized PayloadHolder getInstance(){ 
    if(holder == null){ 
     holder = new PayloadHolder(); 
    } 
    return holder; 
} 

public static synchronized void initPayload(){  
    PayloadHolder.payload = new PayloadDTO();  
} 
public static synchronized PayloadDTO getPayload() { 
    return payload; 
} 
public static synchronized void setPayload(PayloadDTO p) { 
    PayloadHolder.payload = p; 
} 

} 

public class PayloadService extends Service { 

    private static PayloadHolder payloadHolder = PayloadHolder.getInstance(); 

    public static void initPayload(){   
      PayloadHolder.initPayload();   
    } 

    public static void setPayload(PayloadDTO payload){  
     PayloadHolder.setPayload(payload);  
    } 

    public static PayloadDTO getPayload() {  
    return PayloadHolder.getPayload();  
    } 

    ... 

합법적이 방법이다 : t는 응답을 바탕으로 자사의 타이머 작업

을 수행에서 PayloadService을 잠 그려면, 나는 다음에 리팩토링? 나는 이런 식으로하거나 Hardcoded에 언급 된 AtomicReference 접근법을 사용하는 것이 더 나은지 궁금하다. - PayloadService가 실행되는 동안에 만 jvm에서 PayloadHolder 클래스에 대한 참조를 유지하기 위해 PayloadService에 PayloadHolder의 인스턴스를 유지하고 있습니다.

답변

1

당신은 클래스 또는 명시 적으로 모니터에 동기화 할 수 있습니다.

sychnronize를 사용하여 스레드 안전성 가져 오기 및 설정 (예 : volatileAtomicReference)을 사용한다고 가정하면 다른 두 가지 방법이 있습니다.

휘발성

volatile 키워드는 변수를 읽고 할당하는 것은 CPU에 지역 레지스터에 의해 최적화되지 않고 원자 적으로 수행하는 것을 의미 변수 원자에 대한 액세스를 만들 것입니다.

AtomicReference

AtomicReference 변수 같은 기준 원자 액세스를 허용 java.util.concurrent.atomic 패키지의 특별한 클래스이다. volatile과 매우 유사하지만 compareAndSet과 같은 몇 가지 추가 원자 작업을 제공합니다.

예 :

public class PayloadService extends Service { 

private static final AtomicReference<PayloadDTO> payload 
      = new AtomicReference<PayloadDTO>(new PayloadDTO()); 

public static void setPayload(PayloadDTO payload){ 
    PayloadService.payload.set(payload); 
} 

public static PayloadDTO getPayload() { 
    return PayloadService.payload.get ; 
} 

편집 :

당신의 홀더는 당신이 클래스를 인스턴스화되기 때문에 정적 메소드를 호출하는 매우 혼란스러워 보인다. AtomicReference로 고정하려고 시도 :

public class PayloadHolder { 

    private static AtomicReference<PayloadHolder> holder = new AtomicReference<PayloadHolder();  

    //This should be fetched through the holder instance, so no static 
    private AtomicReference<PayloadDTO> payload = new AtomicReference<PayloadDTO>(); 

    private PayloadHolder(){   
    } 

    public static PayloadHolder getInstance(){ 
    PayloadHolder instance = holder.get(); 

    //Check if there's already an instance 
    if(instance == null){ 

     //Try to set a new PayloadHolder - if no one set it already. 
     holder.compareAndSet(null, new PayloadHolder()); 
     instance = holder.get(); 

    } 
    return instance; 
    } 

    public void initPayload(){  
    payload.set(new PayloadDTO()); 

    //Alternative to prevent a second init: 
    //payload.compareAndSet(null, new PayloadDTO()); 
    } 

    public PayloadDTO getPayload() { 
    return payload.get; 
    } 

    public void setPayload(PayloadDTO p) { 
    payload.set(p); 
    } 

} 

public class PayloadService extends Service { 

    private final PayloadHolder payloadHolder = PayloadHolder.getInstance(); 

    public void initPayload(){   
    payloadHolder.initPayload();   
    } 

    public void setPayload(PayloadDTO payload){  
    payloadHolder.setPayload(payload);  
    } 

    public PayloadDTO getPayload() {  
    return payloadHolder.getPayload();  
    } 
} 
+0

제안 된 AtomicReference 구현이 원래 질문에 게시 한 리팩터링 된 예제와 비교하여 더 나은 성능을 선호합니까? – JohnRock

+0

하나의 변수 만 설정할 경우 volatile/AtomicReference를 사용하는 것이 좋습니다. 자물쇠가 없으므로 스케일링이 훨씬 좋습니다. Volatile 및 AtomicReference는 원자 CPU 명령을 사용하여 구현되므로 전체 동기화 (레지스터 플러시 및 업데이트, 잠금 획득)에 대한 오버 헤드가 없습니다. – Hardcoded

+0

제안 해 주셔서 감사합니다. 귀하의 메서드 getInstance()에 혼란 스러워요. check (holder == null)을 확인하기 전에 holder.get()을 호출합니다. 그게 의도적 인거야? 그게 NPE를 생산할 수 있습니까? – JohnRock

3

코드는 다음과 같아야합니다

public static void setPayload(PayloadDTO payload){ 
    synchronized(PayloadService.class){ 
     PayloadService.payload = payload; 
    } 
} 

public static PayloadDTO getPayload() { 
    synchronized(PayloadService.class){ 
     return PayloadService.payload ; 
    } 
} 

원래 코드는 방법이 고정되지 않은 경우에도 근무하지 않을 것입니다. 그 이유는 변경중인 페이로드 인스턴스에서 동기화를 수행했기 때문입니다. 당신은 당신이 현재 실행하려면 다른 동기화 정적 블록이있는 경우에만 문제가 전체 클래스를 잠금 :

업데이트, 응답은 코멘트를 johnrock합니다. 당신을 돕기 위해 많은 사전 구축 된 클래스가 java.util.concurrent에서 더 복잡한 동시성 패턴 모양이 필요한 경우,

public static final Object myLock = new Object(); 

public static void setPayload(PayloadDTO payload){ 
    synchronized(myLock){ 
     PayloadService.payload = payload; 
    } 
} 

public static PayloadDTO getPayload() { 
    synchronized(myLock){ 
     return PayloadService.payload ; 
    } 
} 

또는 : 여러 독립적 인 고정 섹션이 싶은 경우에 당신이 이런 식으로 뭔가를 제안한다.

+2

이것은 public static synchronized methods를 작성하는 것과 동일하지 않습니까? –

+0

그렇습니다. 그러나 동기화 할 필요가없는 코드가 더 많은 경우 동기화 된 범위를 유지했습니다. – bramp

+0

변경중인 인스턴스를 동기화 할 때 문제가 있습니까? – OscarRyz

1

제 질문은 정적 필드에서 동기화하는 경우 해당 필드가 속한 클래스를 잠급니다. 이는 동기화 된 정적 방법과 비슷합니까? 아니면 필드 자체 만 잠글 것입니까?

아니, 그냥 객체 자체 (클래스가 전체 클래스없는 속성)

이 올바른/허용되는 접근 방식에서 고정?

아마도 java.util.concurrent.lock 패키지를 살펴볼 수 있습니다.

클래스 속성에 동기화하는 것을 정말 좋아하지 않지만, 단지 teste의 문제라고 생각합니다.

+1

올바른지, 개체 자체를 잠급니다. 나는 "그것이 필드를 잠그고있다"고 말하는 것이 충분히 명확하지는 않다. –

+0

+1 당신 말이 맞아요 .... 객체 자체를 잠급니다. – OscarRyz

+0

클래스 속성에서 동기화하려는 이유는 getPayload를 호출해야하는 스레드가 페이로드를 업데이트하는 PayloadService의 인스턴스를 가지고 있지 않기 때문입니다. 이는 별도의 스레드로 실행되고 있습니다. 이 작업을 수행하는 더 좋은 방법이 있습니까? – JohnRock

0

이 접근법이 스레드 세이프 (thread-safe)인지 아닌지에 영향을주는 게시되지 않은 기능의 주요 부분이 있습니다 :이 클래스의 다른 부분에서 어떻게 액세스합니까 (PayloadDTO) ?

payload의 다른 인스턴스에서 다른 스레드가 payload 개체를 사용하는 코드를 실행하는 동안 스왑 할 수있는 방법을 제공하는 경우 스레드로부터 안전하지 않습니다.

이 클래스의 주요 작업을 수행하고 payload에 메소드를 호출하는 방법 execute()이있는 경우, 다른 스레드가 execute()를 실행 중입니다 동안 하나 개의 스레드가 setter 메소드로 payload 인스턴스를 변경할 수 있는지 확인해야합니다 예를 들어

.

즉, 공유 상태가되면 모두 상태에서 읽기 및 쓰기 작업을 동기화해야합니다.

개인적으로 나는이 접근법을 이해하지 못하고 그것을 취하지 않을 것입니다 - 다른 스레드가 클래스 분리를 ​​허용하도록 정적 메소드를 제공하면 우려 사항을 위반하는 것처럼 냄새가납니다.

+0

필자는 필로드에 대한 유일한 액세스가 getPayload() 및 setPayload() 메소드를 통해 이루어지는 것으로 가정했습니다. 하지만 당신이이 접근법을 경멸한다면 나는 듣고 싶어합니다. 페이로드 객체를 공유하는 올바른 방법은 무엇입니까? 나는이 문제를 다루기위한 적절한 방법을 찾고있다. – JohnRock

1

이 방법이 올바른지/수용 가능한 방법입니까?

아니요,이 이유는 값을 변경할 수있는 변수/필드에서 절대로 동기화하면 안된다는 것입니다. 즉, PayloadService.payload에서 동기화하고 새 PayloadService.payload를 설정하면 동기화의 황금률을 위반하는 것입니다.

클래스 인스턴스에서 동기화하거나 임의로 private static final Object lock = new Object()을 만들고 동기화해야합니다. 클래스 동기화와 동일한 효과를 얻을 수 있습니다. 변경되지 않는 다른 정적 객체에

+0

나는 "가치를 바꿀 수있는 ** 변수 **에 결코 동기화하지 말라"고 말하고 싶다고 생각합니다. – Hardcoded

+0

@Hardcoded 네 필드/변수가 아니라 객체. –

1

동기화는 : 다른 게시물에서 언급 한 바와 같이

public class PayloadService extends Service { 


private static PayloadDTO payload = new PayloadDTO(); 

private static final Object lock = new Object(); 


public static void setPayload(PayloadDTO payload){ 
    synchronized(lock){ 
     PayloadService.payload = payload; 
    } 
} 

public static PayloadDTO getPayload() { 
    synchronized(lock){ 
     return PayloadService.payload ; 
    } 
} 
관련 문제