2011-10-10 5 views
1

나는 액세스를 동기화해야하는 정적지도가 있습니다. 지도에는 사용자 ID가 입력됩니다. 같은 사용자 ID와 관련된 스레드 만 차단할 수있는 모든 스레드를 차단하지 않도록 동기화를 최적화하려고합니다.정적지도의 항목과 Java 동기화

private static Object s_lock = new Object(); 
private static Map<String,User> s_users = new HashMap(); 
... 
private someMethod() { 
    synchronized(s_lock) 
    { 
     // keeping the global lock for as little as possible 
     user=getMapEntry(); 
    } 
    synchronized(user) <-------- (1) 
    { 
     // time consuming operation 
     // hopefully only blocking threads that relate to same user id. 
    } 
} 
... 
private User getMapEntry(String userId) 
{ 
    if (s_users.containsKey(userId)) { 
     user = s_users.get(userId); 
    } 
    else { 
     user = new User(); 
     user.id = userId; 
     s_users.put(userId, user); 
    } 
    return user; 
} 

내 질문은 - (1) 나는 내가 '글로벌'동기화 잠금을 보유하지만, s_users지도가 정적이기 때문에, 난 여전히 유지하고 있음을 의미 효과적으로 정적 항목입니다 아니에요 가정 오전 전역 잠금 (즉, 클래스 개체 동기화)?

+0

ConcurrentHashMap.putIfAbsent()를 사용하면 첫 번째 동기화 블록을 피할 수 있습니다. –

답변

1

아니요, 각지도 항목이 별도의 개체이므로 맵 항목에서 동기화가지도 또는지도를 소유 한 클래스에서 동기화되지 않습니다.

은 (그건 그렇고, 당신의 #getMapEntry (...) 메소드는 실제로는 오히려 항목보다는 을 반환합니다.지도 항목이 모두 키와 값에 대한 참조를 포함합니다.)

+0

사용자가지도의 핵심 인 경우 구조적으로 키를 어떻게 바꿀 수 있습니까? 이게 어떻게 안전하지 않니? –

+1

그는 사용자를 삽입하거나받지 못합니까? – MasterCassim

+0

(참고 : Hemal Pandya와 MasterCassim은 이전 버전의 답변에 응답하고 있습니다. 이는 완전히 잘못되었습니다. 질문을 완전히 잘못 읽었습니다.) – ruakh

0

이렇게하면됩니다. 두 번째 블록은이 사용자에 대해서만 동기화됩니다.

작은 시험 :

public class Test { 

    protected class User { 
     String id; 
    } 
    private static Object s_lock = new Object(); 
    private static Map<String,User> s_users = new HashMap<String, User>(); 

    public void someMethod(String id) throws InterruptedException { 
     User user; 
     synchronized(s_lock) 
     { 
      // keeping the global lock for as little as possible 
      user = getMapEntry(id); 
     } 
     synchronized(user) 
     { 
      System.out.println("Waiting for user: "+user.id); 
      Thread.sleep(10000); 
     System.out.println("Finished waiting for user: "+user.id); 
     } 
    } 

    private User getMapEntry(String userId) 
    { 
     User user; 

     if (s_users.containsKey(userId)) { 
      user = s_users.get(userId); 
     } 
     else { 
      user = new User(); 
      user.id = userId; 
      s_users.put(userId, user); 
     } 

     return user; 
    } 

    public static void main(String[] args) throws InterruptedException { 
     final Test test = new Test(); 

     for(int i = 0; i < 10; i++) { 
      final int j = i; 

      new Thread() { 
       public void run() { 
        try { 
         test.someMethod(String.valueOf(j % 5)); 
        } catch (InterruptedException e) {} 
       } 
      }.start(); 
     } 
    } 
} 

출력 :

Waiting for user: 0 
Waiting for user: 1 
Waiting for user: 3 
Waiting for user: 2 
Waiting for user: 4 
Finished waiting for user: 0 
Finished waiting for user: 1 
Waiting for user: 1 
Waiting for user: 0 
Finished waiting for user: 3 
Finished waiting for user: 2 
Waiting for user: 3 
Waiting for user: 2 
Finished waiting for user: 4 
Waiting for user: 4 
Finished waiting for user: 0 
Finished waiting for user: 1 
Finished waiting for user: 3 
Finished waiting for user: 2 
Finished waiting for user: 4 

그래서 괜찮아요.

0

당신은 클래스 객체에 동기화 중되지 않는다 그러나 나는 무엇을하고 있는지 생각하고있다 :

  • 다른 스레드가 공유 s_lock를 사용하여지도에 접근하고 있습니다. 하지만 당신은 단지뿐만 아니라 같은 User 개체가 순서대로 "작업 소모 시간을"실행에 접근하는

  • 다른 스레드에서 s_usersfinalsynchronize을 할 수 있다고 생각합니다. 개체가 같은 User의지도에 계속 변경되는 경우

내가 볼주의의 유일한 지점입니다. 당신이 하나만 에 대한 하나의 User 개체를 가지고있는 한 당신은 괜찮습니다.

나는 비슷한 것을했지만 userId.intern()에 동기화했다. 그것은 단점이 있지만 내 경우에는 잘 작동합니다.