2011-10-26 2 views
2

synchronizedMap의 이름이 doMapOperation 인 동기화 목록을 가지고 있습니다. 이 함수에서는지도에서 항목을 추가/제거하고 이러한 객체에서 값 비싼 연산을 수행해야합니다. 동기화 된 블록에서 값 비싼 연산을 호출하고 싶지는 않지만 이러한 연산을 수행하는 동안지도가 일관된 상태인지 확인하는 방법을 알지 못합니다. 이 작업을 수행하는 올바른 방법은 무엇입니까?값 비싼 작업으로 인한 콜렉션에서의 Java 동기화

이렇게하면 동기화 블록에 비용이 많이 드는 작업을 호출 피하려고하기 때문에 나는 확신 내 초기 레이아웃이 잘못이다 : 당신이 이런 짓을 했을까하는 방법,

public void doMapOperation(Object key1, Object key2) { 
    synchronized (synchronizedMap) { 

     // Remove key1 if it exists. 
     if (synchronizedMap.containsKey(key1)) { 
      Object value = synchronizedMap.get(key1); 
      value.doExpensiveOperation(); // Shouldn't be in synchronized block. 

      synchronizedMap.remove(key1); 
     } 

     // Add key2 if necessary. 
     Object value = synchronizedMap.get(key2); 
     if (value == null) { 
      Object value = new Object(); 
      synchronizedMap.put(key2, value); 
     } 

     value.doOtherExpensiveOperation(); // Shouldn't be in synchronized block. 
    } // End of synchronization. 
} 

내가이 질문의 연속으로 생각 루프에?

public void doMapOperation(Object... keys) { 
    synchronized (synchronizedMap) { 

     // Loop through keys and remove them. 
     for (Object key : keys) { 
      // Check if map has key, remove if key exists, add if key doesn't. 
      if (synchronizedMap.containsKey(key)) { 
       Object value = synchronizedMap.get(key); 
       value.doExpensiveOperation(); // Shouldn't be here. 

       synchronizedMap.remove(key); 
      } else { 
       Object value = new Object(); 
       value.doAnotherExpensiveOperation(); // Shouldn't here. 

       synchronizedMap.put(key, value); 
      } 
     } 
    } // End of synchronization block. 
} 

감사합니다.

+1

느린 작업 중에 잠금을 해제 할 수 있는지 여부를 알 수있는 정보가 충분하지 않습니다. 중요한 질문은이 모든 작업이 다른 스레드에 원자 적으로 발생해야하는지 여부입니다. 그렇지 않으면 어떤 작업이 원자 적으로 나타나야합니까? 그리고 작업은 전체 맵에서 원자 적으로 발생해야합니까? 즉, 단일 키에 대한 작업이 원자적일 수 있도록 보장 할 수 있다면 어떨까요? – erickson

답변

2

당신과 같이 동기화 된 블록 밖에 고가의 작업을 수행 할 수 있습니다

public void doMapOperation(Object... keys) { 
    ArrayList<Object> contained = new ArrayList<Object>(); 
    ArrayList<Object> missing = new ArrayList<Object>(); 

    synchronized (synchronizedMap) { 
     if (synchronizedMap.containsKey(key)) { 
      contained.add(synchronizedMap.get(key)); 
      synchronizedMap.remove(key); 
     } else { 
      missing.add(synchronizedMap.get(key)); 
      synchronizedMap.put(key, value); 
     } 
    } 

    for (Object o : contained) 
     o.doExpensiveOperation(); 
    for (Object o : missing) 
     o.doAnotherExpensiveOperation(); 
} 

유일한 단점은 그들이 synchronizedMap에서 제거 된 후 값에 대한 작업을 수행 할 수 있습니다.

1

첫 번째 스 니펫에서 : if 절에서 두 값을 선언하고 if 절에 값을 할당하십시오. if 절을 동기화시키고 외부에서 값 비싼 조작을 호출하십시오.

두 번째 경우에는 동일하지만 루프 내부에서 수행하십시오. (루프 내에서 동기화 됨). 물론 루프 외부에 synchronized 문을 하나만 가질 수 있으며 값 비싼 연산을 호출 할 객체의 List을 간단히 채 웁니다. 그런 다음 두 번째 루프에서 동기화 된 블록 외부에서 목록의 모든 값에 대해 해당 연산을 호출합니다.

+0

하나의 방법으로 여러 개의 동기화 된 블록을 사용하는 것에 대해 어떤 조치가 있습니까? 두 번째 경우 수백 시간을 반복하면 수백 번 동기화됩니다. 그게 나쁜가요? –

+0

100 개의 작업을 동기화해야하는 경우 백개의 동기화가 필요합니다. – Bozho

2

당신은 당신의 synchronizedMap에 대한 래퍼를 작성하고 작업 containsKey, remove처럼 확인하고 put 동기화 방법입니다 수 있습니다. 그러면 맵에 대한 액세스 만 동기화되고 비싼 작업은 동기화 된 블록 외부에서 발생할 수 있습니다.

다른 장점은 동기화 된 블록 외부에서 값 비싼 작업을 유지하면 작업에서 다른 동기화 된 맵 메서드를 호출 할 때 교착 상태 위험을 피할 수 있습니다.

+0

래퍼가 호출 될 때 해당 메서드를 동기화하는 데 도움이된다는 것을 알고 있지만 sychronizedMap을 수정하는 동안지도가 다른 곳에서 수정되지 않도록하고 싶습니다. –

1

실제로 동기화를 한 번만하면됩니다. 첫 번째 제거가 아마도 가장 쉽습니다. 객체가 존재한다는 것을 알고 있고 제거가 원 자성이라면 왜 그것을 제거하지 않고 반환되는 객체가 null이 아닌지 비싼 연산을 호출해야합니까? 풋를 들어

// Remove key1 if it exists. 
     if (synchronizedMap.containsKey(key1)) { 
      Object value = synchronizedMap.remove(key1); 
      if(value != null){ //thread has exclusive access to value 
       value.doExpensiveOperation(); 
      } 
     } 

, 비싼이며, 당신이 운이 꽤 많이하고 액세스를 동기화 할 필요가 원자 수 있어야하기 때문이다. 어떤 종류의 컴퓨팅 맵을 사용하는 것이 좋습니다.구글 - 컬렉션을보고 MapMaker을 가지고

ConcurrentMap<Key, ExpensiveObject> expensiveObjects = new MapMaker() 
     .concurrencyLevel(32) 
     .makeComputingMap(
      new Function<Key, ExpensiveObject>() { 
      public ExpensiveObject apply(Key key) { 
       return createNewExpensiveObject(key); 
      } 
      }); 

이 모두에서 memoization

의 한 형태 simlpy입니다 당신은 예를 들어 키에 따라 고가의 객체를 구축 할 것 인 ConcurrentMap를 만들 수 있습니다

이러한 경우에는 synchronized을 전혀 사용할 필요가 없습니다 (적어도 명시 적으로)

1

작은 효율성 es, 시간의 약 97 %를 말하십시오 : 조기 최적화는 모든 악의 뿌리입니다. 그러나 우리는 그 중요한 3 %에 우리의 기회를 전달해서는 안됩니다. 좋은 프로그래머는 그러한 추론에 의해 만족하지 못하도록 하지 않을 것이며, 그는 현명하게 를 중요한 코드에서 보게 될 것입니다. 그 코드가 발견 된 후에 만 ​​ 이 확인되었습니다. - Donald Knuth

하나의 메소드 doMapOperation()이 있습니다. 이 방법이 계속해서 블록 동기화되면 성능은 어떻게됩니까? 당신이 모르는 경우에 당신은 좋은 실행 해결책을 언제 가지고 있는지 어떻게 알 것입니까? 지도에서 제거 된 후에도 비싼 작업을 여러 번 호출 할 준비가 되었습니까?

나는 당신이 전달한 것보다 더 나은 문제를 잘 이해하고 있기 때문에 겸손 해지고 싶지는 않지만 준비가되지 않은 최적화 수준으로 뛰어 들고있는 것처럼 보입니다. 필요하지 않습니다. 당신이 Map에 null 값이없는 경우

1

, 당신은 전혀 containsKey() 전화를 필요가 없습니다 당신이 항목을 제거하고이 있었는지 당신에게 모두 Map.remove()를 사용할 수 있습니다. 당신이 값 동안 보는지도의 다른 클라이언트를 신경 쓰지 않을 경우 비용이 많이 드는 작업 자체가 동기화 할 즉, 필요하지 않는 경우

Object value = Map.remove(key); 
if (value != null) 
    value.doExpensiveOperation(); 
else 
{ 
    value = new Value(); 
    value.doExpensiveOperation(); 
    map.put(key,value); 
} 

: 그래서 동기화 된 블록의 실제 내용은이를 할 필요가 그것이 작동되고 있다면 다음과 같이 더 간단하게 할 수 있습니다 :

Object value = Map.remove(key); 
if (value == null) 
{ 
    value = new Value(); 
    map.put(key,value); 
} 
value.doExpensiveOperation(); 

그리고 비싼 조작 전에 동기화 된 블록을 종료 할 수 있습니다.