2010-03-11 3 views
9

이것은 더 복잡한 시스템으로 작업 할 때 자주 겪는 문제이며 해결 방법을 찾지 못했습니다. 일반적으로 공유 객체의 주제에 대한 변형을 포함하며, 그 구성과 초기화는 반드시 두 단계로 구분됩니다. 이것은 일반적으로 애플릿과 유사한 아키텍처 요구 사항 때문에 발생하므로 구성 및 초기화를 통합하라는 대답은 유용하지 않습니다. 시스템은 늦어도 Java 4를 대상으로해야하기 때문에 나중에 JVM에서만 지원되는 지원은 유용하지 않습니다. 이 시작할 때까지 설정 할 수 없기 때문에Java에서 thread-safe 한 번만 읽기 - 값을 만드는 방법은 무엇입니까?

public class MyClass 
{ 

private /*ideally-final*/ SomeObject someObject; 

MyClass() { 
    someObject=null; 
    } 

public void startup() { 
    someObject=new SomeObject(...arguments from environment which are not available until startup is called...); 
    } 

public void shutdown() { 
    someObject=null; // this is not necessary, I am just expressing the intended scope of someObject explicitly 
    } 
} 

내가 여기서 someObject 결승전을 할 수 없습니다 :

예로서

,의 난과 같이 응용 프로그램 프레임 워크에 맞게 구성되어 클래스가 있다고 가정 해 보자()가 호출됩니다. 그러나 나는 그것의 write-once 의미를 반영하고 다중 스레드에서 직접 액세스 할 수 있어야하며, 바람직하게는 동기화를 피하는 것이 좋습니다.

표현하고 finalness의 정도를 적용 할 수있는 아이디어, 내가 같은, 일반적인 컨테이너를 만들 수 있다고 추측 (UPDATE -이 클래스의 수정 스레딩 sematics) :

public class WormRef<T> 
{ 
private volatile T      reference;        // wrapped reference 

public WormRef() { 
    reference=null; 
    } 

public WormRef<T> init(T val) { 
    if(reference!=null) { throw new IllegalStateException("The WormRef container is already initialized"); } 
    reference=val; 
    return this; 
    } 

public T get() { 
    if(reference==null) { throw new IllegalStateException("The WormRef container is not initialized"); } 
    return reference; 
    } 

} 

다음 MyClass에서, 위, 수행

    : 나를 위해 몇 가지 질문을 제기

    private final WormRef<SomeObject> someObject; 
    
    MyClass() { 
        someObject=new WormRef<SomeObject>(); 
        } 
    
    public void startup() { 
        someObject.init(new SomeObject(...)); 
        } 
    
    public void sometimeLater() { 
        someObject.get().doSomething(); 
        } 
    

  1. 더 나은 방법이나 기존 Java 객체 (Java 4에서 사용할 수 있어야합니까?)가 있습니까?

둘째로, 스레드 안전의 관점에서 :

  1. 이 스레드 안전 제공하는 다른 스레드가 set()가 호출 된 때까지 someObject.get()에 액세스하지 않습니다. 다른 스레드는 startup()과 shutdown() 사이의 MyClass에서만 메서드를 호출합니다. 프레임 워크가이를 보장합니다.
  2. 완전히 동기화되지 않은 WormReference 컨테이너가 주어지면 어느 JMM에서도 null이 아니며 SomeObject에 대한 참조도 아닌 object 값을 볼 수 있습니까? 즉, JMM은 객체가 할당되었을 때 힙에 발생한 모든 값이 스레드가 객체의 메모리를 관측 할 수 없음을 항상 보장합니다. 할당은 명시 적으로 할당 된 메모리를 0으로 만들기 때문에 대답은 "예"라고 생각하지만 주어진 메모리 위치에서 관찰되는 CPU 캐싱 결과가 있습니까?
  3. 적절한 다중 스레드 의미론을 보장하기 위해 WormRef.reference를 volatile 화하는 것으로 충분합니까?

주이 질문의 주요 추력 표현하고 수있는없이 finalness에게 someObject의을 적용 하는 방법은 실제로 그것을 final 표시; 보조는 thread-safety에 필요한 것입니다. 즉, 이것의 스레드 안전 측면에 너무 매달 리지 마십시오.다음과 같이

+0

은 허용 될 수 없습니다? – MSN

+0

@MSN : 사실 대부분의 경우 다른 스레드는 초기화가 완료 될 때까지 시작되지 않습니다. –

+0

OP 모두가 내 질문에 대한 답을 모욕 한 것이므로 대답을 삭제했습니다. 이것은 말이되지 않습니다. – BalusC

답변

0

이것은 내 최종 답변, 레지스 1 : "당신이 백만장자가되고 싶어 그래서"

/** 
* Provides a simple write-one, read-many wrapper for an object reference for those situations 
* where you have an instance variable which you would like to declare as final but can't because 
* the instance initialization extends beyond construction. 
* <p> 
* An example would be <code>java.awt.Applet</code> with its constructor, <code>init()</code> and 
* <code>start()</code> methods. 
* <p> 
* Threading Design : [ ] Single Threaded [x] Threadsafe [ ] Immutable [ ] Isolated 
* 
* @since   Build 2010.0311.1923 
*/ 

public class WormRef<T> 
extends Object 
{ 

private volatile T      reference;        // wrapped reference 

public WormRef() { 
    super(); 

    reference=null; 
    } 

public WormRef<T> init(T val) { 
    // Use synchronization to prevent a race-condition whereby the following interation could happen between three threads 
    // 
    // Thread 1  Thread 2  Thread 3 
    // --------------- --------------- --------------- 
    // init-read null 
    //     init-read null 
    // init-write A 
    //         get A 
    //     init-write B 
    //         get B 
    // 
    // whereby Thread 3 sees A on the first get and B on subsequent gets. 
    synchronized(this) { 
     if(reference!=null) { throw new IllegalStateException("The WormRef container is already initialized"); } 
     reference=val; 
     } 
    return this; 
    } 

public T get() { 
    if(reference==null) { throw new IllegalStateException("The WormRef container is not initialized"); } 
    return reference; 
    } 

} // END PUBLIC CLASS 

(1) 게임 쇼을 부여, 레지스 Philburn 주최.

1

이론적으로는 startup()를 다시 작성하기에 충분 것이다 : 그런데

public synchronized void startup() { 
    if (someObject == null) someObject = new SomeObject(); 
} 

WoRmObject 최종 있지만, 스레드가 여전히 set() 여러 번 호출 할 수 있습니다. 동기화를 추가해야합니다.

갱신 : 나는 그것의 둘레에 약간의 연주와 SSCCE, 당신이 찾을 수 유용 그것으로 주위에 조금 재생 :

package com.stackoverflow.q2428725; 

import java.util.concurrent.Callable; 
import java.util.concurrent.CountDownLatch; 
import java.util.concurrent.Executors; 
import java.util.concurrent.Future; 
import java.util.concurrent.ScheduledExecutorService; 
import java.util.concurrent.TimeUnit; 

public class Test { 

    public static void main(String... args) throws Exception { 
     Bean bean = new Bean(); 
     ScheduledExecutorService executor = Executors.newScheduledThreadPool(4); 
     executor.schedule(new StartupTask(bean), 2, TimeUnit.SECONDS); 
     executor.schedule(new StartupTask(bean), 2, TimeUnit.SECONDS); 
     Future<String> result1 = executor.submit(new GetTask(bean)); 
     Future<String> result2 = executor.submit(new GetTask(bean)); 
     System.out.println("Result1: " + result1.get()); 
     System.out.println("Result2: " + result2.get()); 
     executor.shutdown(); 
    } 

} 

class Bean { 

    private String property; 
    private CountDownLatch latch = new CountDownLatch(1); 

    public synchronized void startup() { 
     if (property == null) { 
      System.out.println("Setting property."); 
      property = "foo"; 
      latch.countDown(); 
     } else { 
      System.out.println("Property already set!"); 
     } 
    } 

    public String get() { 
     try { 
      latch.await(); 
     } catch (InterruptedException e) { 
      // handle. 
     } 
     return property; 
    } 

} 

class StartupTask implements Runnable { 

    private Bean bean; 

    public StartupTask(Bean bean) { 
     this.bean = bean; 
    } 

    public void run() { 
     System.out.println("Starting up bean..."); 
     bean.startup(); 
     System.out.println("Bean started!"); 
    } 

} 

class GetTask implements Callable<String> { 

    private Bean bean; 

    public GetTask(Bean bean) { 
     this.bean = bean; 
    } 

    public String call() { 
     System.out.println("Getting bean property..."); 
     String property = bean.get(); 
     System.out.println("Bean property got!"); 
     return property; 
    } 

} 

CountDownLatch가 될 때까지 차단하는 모든 await() 전화의 원인이됩니다를 생성 카운트 다운은 0에 도달합니다.

+1

get()도 동기화되지 않으면 Not입니다. –

+0

내가 잘못 생각할 수도 있지만, 그것이 보장 될 때 시작이 모든 스레드가 get()에 액세스하기 전에 완료되었다고 생각하면 get 동기화가 충분할 수 있습니다. –

+0

@Alexander : 공정한 지적. 'someObject'가 더 이상 null이 아닐 때까지'get()'을 막고 싶습니다. 비 동기화 (그리고 불쾌한?) 방법은'while (someObject == null);'을 get() 메소드의 맨 위에 추가하는 것입니다. – BalusC

0
  1. 방법 동기화에 대한?
  2. 아니요 스레드로부터 안전하지 않습니다. 동기화가 없으면 변수의 새 상태가 절대로 다른 스레드와 통신되지 않을 수 있습니다.
  3. 예, 참고 자료는 원산지이므로 널 또는 참조가 표시됩니다. 참조 된 개체의 상태가 someObjectvolatile를 선언하여 완전히 다른 이야기
1

내가 시작할 것입니다 있습니다.

private volatile SomeObject someObject; 

휘발성 키워드는 someObject를 참조 할 때 항상 업데이트 된 메모리를 볼 수 있습니다 별도의 스레드를 의미 메모리 장벽을 만듭니다.

현재 구현에서 startup이 호출 된 후에도 일부 스레드는 여전히 null이라는 someObject를 볼 수 있습니다.

실제로이 volatile 기술은 java.util.concurrent 패키지에 선언 된 컬렉션에 많이 사용됩니다.

다른 포스터가 여기 제시 한 것처럼 다른 모든 포스터가 전체 동기화로 실패하면 실패합니다.

+1

Volitale은'get()'이'startup() '전에 호출되는 것을 막지 않습니다. 참조가 동시에 액세스되는 것을 막을뿐 (즉, 액세스가 동기화 됨) '동기화'된 것처럼 get()에서 성능 저하를 일으킬 수 있습니다. – BalusC

+0

@BalusC :'init()'전에'get()'을 호출하는 것이 좋은 지적입니다; getter에서도 null 테스트를해야 할 것입니다. –

1

나는 if (object != null)

+0

감사합니다. 실제로 설정되지 않았는지 확인하려고했습니다. 그리고 나는'init()'제안을 매우 좋아한다. (실제로 이것은 일회성 초기화를 위해 내가 일반적으로하는 일이다.) 코드가 업데이트되었습니다. –

0

당신이 각 스레드의 값을 한 번에 설정 될 수있는의 ThreadLocal을 사용 할 수 예외를 throw() 메소드를 동기화 초기화를 WoRmObject에 setter 메소드를 제거하고 제공 할 것?

+0

그게 작동하지 않을 것이다; 객체는 여러 스레드간에 공유됩니다. –

1

당신이 만들려고하고이 객체 컨테이너의 대리인으로 AtomicReference을 사용하는 것이 좋습니다. 예 :

public class Foo<Bar> { 
private final AtomicReference<Bar> myBar = new AtomicReference<Bar>(); 
public Bar get() { 
    if (myBar.get()==null) myBar.compareAndSet(null,init()); 
    return myBar.get(); 
} 

Bar init() { /* ... */ } 
//... 
} 

EDITED : 일부 지연 초기화 방법으로 한 번 설정됩니다. init() (아마도 값 비싼) 전화를 여러 번 차단하는 데는 적합하지 않지만 더 악화 될 수 있습니다. myBar의 인스턴스화를 생성자에 고수하고 올바른 정보가 제공되면 할당을 허용하는 생성자를 나중에 추가 할 수 있습니다.

예를 들어, this site에서 스레드 안전, 싱글 톤 인스턴스화 (문제와 매우 유사)에 대한 일반적인 설명이 있습니다.

+0

좋은 제안 - +1. 그러나 후드 아래의 AtomicReference는 변동성이있는 참조를 유지하기 때문에이 경우에는 참조를 휘발성으로 만드는 것에 비해 아무런 이득도 얻지 못하는 것처럼 이중 포장하는 것처럼 보입니다. 또한,'init()'은 항상 비싸지 않다. 공사가 끝날 때까지 연기해야한다는 것입니다. –

+0

확실하지만, init() 등과 같은 다양한 테스트와 관련된 상용구 로직을 처리합니다. – Carl

+0

어디로 가는지 알고 있습니다. 하지만 WORM 객체는 자체적으로 초기화 할 수 없기 때문에 포장해야하는 객체와 함께 init()을 호출하기 위해 외부 객체가 필요합니다. –

0

특히 Java에서 지연 인스턴스화를 수행하는 데는 많은 잘못된 방법이 있습니다. 간단히 말해서, 순진한 접근법은 개인 객체, 공개 동기화 된 init 메소드, 그리고 객체에 대해 null 체크를 수행하고 필요한 경우 init을 호출하는 public 동기화되지 않은 get 메소드를 생성하는 것입니다. 문제의 복잡성은 스레드 안전 방식으로 null 검사를 수행하는 데 있습니다.

이 문서는 사용이어야한다

: http://en.wikipedia.org/wiki/Double-checked_locking

이 특정 주제, 자바, 다소 오래된입니다 더그 레아의 '자바에서 동시 프로그래밍'에서 자세히 설명하고, 연습에서 '자바 동시성의 레아와 다른 사람들이 공동 저술했다. 특히 CPJ는 ​​Java 5 출시 이전에 발표 되었기 때문에 Java의 동시성 제어 기능이 크게 향상되었습니다.

나는 집에 돌아 가면 그 책에 대한 액세스 권한이 있습니다.

1

프레임 워크에 대한 설명에서 스레드 안전성이 가장 높습니다. myobj.startup()을 호출하고 을 다른 스레드에서 사용할 수있게 만드는 사이에 메모리 장벽이 있어야합니다. 이렇게하면 startup()의 쓰기가 다른 스레드에서 볼 수있게됩니다. 따라서 프레임 워크가 스레드 안전성 때문에 스레드 안전성에 대해 걱정할 필요가 없습니다. 아무런 무료 점심 식사도 없다. 다른 스레드가 프레임 워크를 통해 myobj에 액세스 할 때마다 동기화 또는 휘발성 읽기가 포함되어야합니다.

프레임 워크를보고 경로의 코드를 나열하면 코드 스레드를 안전하게 만드는 적절한 위치에 sync/volatile이 표시됩니다. 즉, 프레임 워크가 올바르게 구현되었는지 여부입니다.

작업자 스레드가 계산을 수행하고 결과를 전역 변수 x에 저장 한 다음 다시 칠하는 이벤트를 보내는 일반적인 스윙 예제를 살펴 보겠습니다. 재 페인트 이벤트를 수신하면 GUI 스레드는 전역 변수 x에서 결과를 읽고 그에 따라 다시 페인팅합니다.

작업자 스레드도 다시 그리기 코드도 동기화 나 휘발성 읽기/쓰기 작업을 수행하지 않습니다. 이와 같이 수만 개의 구현이 있어야합니다. 다행히도 프로그래머가 특별한주의를 기울이지 않았더라도 모든 스레드는 안전합니다. 왜? 이벤트 대기열이 동기화 되었기 때문에;

write x - insert event - read event - read x 

따라서 x를 작성하고 x가 제대로 암시 이벤트 프레임 워크를 통해 동기화 읽기 : 우리는 좋은 happends-전에 체인을 가지고있다.

+0

좋은 지적. 그리고 실제로 나의 특별한 사용 시나리오에서는 사실입니다. 보다 일반적으로, 나는 여전히 WORM 래퍼의 참조를 휘발성으로 만드는쪽으로 기울어 져 있기 때문에 분할 초기화가 항상 올바르게 동기화 된 프레임 워크의 컨텍스트에있는 것처럼 보이지만 항상 안전합니다. –

0

내 작은 버전은 AtomicReference를 기반으로합니다. 아마 가장 좋은 건 아니지만, 나는 그것을 사용하기 깨끗하고 쉽게 생각하는 :

public static class ImmutableReference<V> { 
    private AtomicReference<V> ref = new AtomicReference<V>(null); 

    public boolean trySet(V v) 
    { 
     if(v == null) 
      throw new IllegalArgumentException("ImmutableReference cannot hold null values"); 

     return ref.compareAndSet(null, v); 
    } 

    public void set(V v) 
    { 
     if(!trySet(v)) throw new IllegalStateException("Trying to modify an immutable reference"); 
    } 

    public V get() 
    { 
     V v = ref.get(); 
     if(v == null) 
      throw new IllegalStateException("Not initialized immutable reference."); 

     return v; 
    } 

    public V tryGet() 
    { 
     return ref.get(); 
    } 
} 
+0

좋지 않습니다 - Java 4에서 실행해야합니다 (질문 참조). –

0

첫 번째 질문 : 왜 그냥 생성자에서 호출 개인 방법을 시작할 수 없습니다, 다음이 될 수 있습니다 결정적인. 이렇게하면 생성자가 반환 된 후에 스레드의 안전성을 보장 할 수 있습니다.또는 시작 메소드가 생성자의 일부로 MyClass 객체를 만들 수 있도록 클래스 구조를 다시 요소 화하십시오. 아마도이 특별한 경우는 가난한 구조의 경우처럼 보일 수 있습니다. 여기서 당신은 단지 그것을 최종적이고 불변으로 만들고 싶습니다.

클래스가 변경 불가능하고 작성된 후에 만 ​​읽을 수있는 쉬운 방법은 구아바의 불변 목록에 넣으십시오. 참조를 반환하라는 메시지가 표시 될 때 방어 적으로 복사하는 자체적 인 불변 래퍼를 만들 수도 있습니다. 따라서 클라이언트가 참조를 변경하지 못하게 할 수 있습니다. 내부적으로 불변 인 경우에는 더 이상 동기화 할 필요가 없으며 비동기 읽기가 허용됩니다. 래퍼를 요청에 따라 방어 적으로 복사하도록 설정할 수 있으므로 쓰기 시도도 깨끗하게 실패합니다 (아무 것도하지 않습니다). 메모리 장벽이 필요하거나 게으른 초기화가 가능할 수도 있습니다. 객체가 생성되는 동안 동기화되지 않은 읽기 요청이 여러 번 발생할 수 있으므로 지연 초기화에는 추가 동기화가 필요할 수 있습니다.

약간 더 복잡한 방법은 열거 형을 사용하는 것입니다. 열거 형은 싱글 톤 (singleton)을 보장하므로 열거 형이 생성되는 즉시 영원히 고정됩니다. 객체가 내부적으로 불변인지 확인해야하지만, 객체의 싱글 톤 상태를 보장합니다. 많은 노력없이.

+0

첫 번째 질문 : "일반적으로 애플릿과 유사한 아키텍처 요구 사항으로 인해 구성 및 초기화를 통합하는 것이 도움이되지 않습니다." –

-1

다음 클래스가 귀하의 질문에 답변 할 수 있습니다. 제공된 스레드의 final 값 보호기와 함께 volatile 중간 변수를 사용하여 일부 스레드 안전성을 얻었습니다. 당신은 synchronized setter/getter를 사용하여 더 증가시킬 수 있습니다. 희망이 도움이됩니다.

나는 "이 초기화 될 때까지 다른 스레드를 시작하지 마십시오"라고 가정 해 봅시다

https://stackoverflow.com/a/38290652/6519864

관련 문제