2016-06-29 2 views
2

나는 간단한 프로젝트를 가지고 있는데, 나는 자바 로킹과 스레딩에 대한 나의 이해를 돕기 위해 노력하고있다. 기본적으로 주어진 나이를 지나서 항목을 제거하는 정리 스레드를 시작하는 캐시 개체가 있습니다. 테스트 클래스 "Tester"는 캐시에 항목을 추가하고 캐시 내용을 출력하는 두 개의 추가 스레드를 실행합니다. 캐시에서 임베드 된 HashMap을 정리 스레드가 수정할 때 어떤 이유로 더 이상 반복을 중지합니다. 접근 자/변이 자 메서드를 동기화하고 캐시의 LOCK 개체를 동기화하려고 시도했습니다. 어떤 아이디어 나 도움이 muy beueno가 될 것입니다.)스레드 크래킹

public class Cache 
{ 
    private HashMap<Object, ObjectWrapper> cachedObjects = new HashMap<>(); 
    private static Cache cache = null; 
    private static int TIME_TO_KEEP = 60000; // 60 seconds 
    private final static Object LOCK = new Object(); 

    public static Cache getInstance() 
    { 
     if (cache == null) 
     { 
      cache = new Cache(); 
     } 
     return cache; 
    } 

    private Cache() 
    { 
     ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); 

     Runnable task =() -> { 
      synchronized(LOCK) 
      { 
       System.out.println("Cleanup"); 
       Set<Object> keys = cachedObjects.keySet(); 
       final long now = System.currentTimeMillis(); 
       keys.forEach(k -> { 
        try 
        { 

         { 
          ObjectWrapper ow = cachedObjects.get(k); 
          if(ow.getExpireTime() < now) 
          { 
           int size = cachedObjects.size(); 
           cachedObjects.remove(k, ow); 
           System.out.println("DEL:" + k + ", from " + size + " to " + cachedObjects.size()); 
          } 
         } 
        } 
        catch (Throwable t) 
        { 
         t.printStackTrace(); 
        } 
       }); 
      } 
     }; 

     executor.scheduleWithFixedDelay(task, 5, 15, TimeUnit.SECONDS); 
    } 

    public void addObject(Object key, Object obj) 
    { 
     synchronized(LOCK) 
     { 
      ObjectWrapper ow = new ObjectWrapper(obj, System.currentTimeMillis() + TIME_TO_KEEP); 
      cachedObjects.put(key, ow); 
     } 
    } 

    public ObjectWrapper getObj(Object key) 
    { 
     synchronized(LOCK) 
     { 
      return cachedObjects.get(key); 
     } 
    } 

    public Collection<ObjectWrapper> getValues() 
    { 
     synchronized(LOCK) 
     { 
      return cachedObjects.values(); 
     } 
    } 

    public Set<Object> getKeys() 
    { 
     synchronized(LOCK) 
     { 
      return cachedObjects.keySet(); 
     } 
    } 

    public static void main(String[] args) 
    { 
     Cache cache = Cache.getInstance(); 
    } 
} 

import java.util.*; 
import java.util.concurrent.*; 

public class Tester 
{ 
    private static Integer id = 0; 
    private static Cache cache = Cache.getInstance(); 

    public static void main(String[] args) 
    { 
     ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); 

     Runnable adder =() -> { 
      System.out.println("Adding id:" + ++id); 
      Object o = new Object(); 
      cache.addObject(id, o); 
     }; 

     executor.scheduleWithFixedDelay(adder, 5, 10, TimeUnit.SECONDS); 


     Runnable tester =() -> { 
      long currTime = System.currentTimeMillis(); 
      System.out.println("Test: "); 
      Set<Object> keys = cache.getKeys(); 
      keys.forEach(k -> { 
       ObjectWrapper o = cache.getObj(k); 
       System.out.println(k + ">" + currTime + "::" + o.getExpireTime()); 
      }); 
     }; 

     executor.scheduleWithFixedDelay(tester, 20, 10, TimeUnit.SECONDS); 
    } 
} 

public class ObjectWrapper 
{ 
    private Object obj; 
    private long expireTime; 

    public ObjectWrapper(Object obj, long expireTime) 
    { 
     this.obj = obj; 
     this.expireTime = expireTime; 
    } 

    public Object getObj() 
    { 
     return obj; 
    } 

    public void setObj(Object obj) 
    { 
     this.obj = obj; 
    } 

    public long getExpireTime() 
    { 
     return expireTime; 
    } 

    public void setExpireTime(long expireTime) 
    { 
     this.expireTime = expireTime; 
    } 
} 

답변

2

는지도 구현 기본적 HashMap 경우 안전하지 않은 스레드 인 ConcurrentHashMap 사용을 고려한다.

귀하의 실수는 주로 여기에 있습니다 :

public Collection<ObjectWrapper> getValues() 
{ 
    synchronized(LOCK) 
    { 
     return cachedObjects.values(); 
    } 
} 

public Set<Object> getKeys() 
{ 
    synchronized(LOCK) 
    { 
     return cachedObjects.keySet(); 
    } 
} 

당신이 액세스를 동기화 할 필요가 있도록, 당신은 키와 실제로 비 스레드 안전 컬렉션을 제공하여 HashMap의 가치를 공유로이 충분하지 않다 이렇게 당신은 그 내용을 반복하거나 단순히 옆에 안전 사본을 반환 할 때

public Collection<ObjectWrapper> getValues() 
{ 
    synchronized(LOCK) 
    { 
     return new ArrayList<>(cachedObjects.values()); 
    } 
} 

public Set<Object> getKeys() 
{ 
    synchronized(LOCK) 
    { 
     return new HashSet<>(cachedObjects.keySet()); 
    } 
} 

당신은 그렇지 않으면 코드가 여전히 스레드되지 않습니다이 공유 할 의미로도 ObjectWrapper 스레드로부터 안전하게하는 것도 필요를 안전한. 이를위한 가장 간단한 방법은 불변 같이하는 것입니다 :

public class ObjectWrapper 
{ 
    private final Object obj; 
    private final long expireTime; 

    public ObjectWrapper(Object obj, long expireTime) 
    { 
     this.obj = obj; 
     this.expireTime = expireTime; 
    } 

    public Object getObj() 
    { 
     return obj; 
    } 

    public long getExpireTime() 
    { 
     return expireTime; 
    } 
} 
+0

감사합니다. HashMap 대신 ConcurrentHashMap을 사용하여 작업을 수행 할 수 있습니다. ObjectWrapper를 단지 중첩 된 객체이고 다른 스레드에 의해 직접 변경되는 것처럼 어떻게 불변으로 만들지 모르겠습니다. 나는 그것이 단지 나의 무지라고 확신한다. – supertorqued

+0

멀티 스레딩 env에서 규칙은 항상 스레드 안전 객체를 공유하므로 ObjectWrapper 유형의 인스턴스를 공유 할 때 클래스는 스레드 안전해야합니다. –

1

은 try-catch 블록 외부 (다음 반복에 던져하는 ConcurrentModificationException를 결과의 반복하는 동안 키 집합에서 요소를 삭제하는). 당신은 루프 외부 try-catch 블록을 이동하는 경우 : 대신

java.util.ConcurrentModificationException 
     at java.util.HashMap$KeySet.forEach(HashMap.java:935) 
     at Cache.lambda$new$1(Cache.java:32) 
     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) 
     at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) 
     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) 
     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) 
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 
     at java.lang.Thread.run(Thread.java:745) 

키 집합에 대한 반복자를 사용하십시오 :

private Cache() 
{ 
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); 

    Runnable task =() -> { 
     synchronized(LOCK) 
     { 
      try 
      { 
       System.out.println("Cleanup"); 
       Set<Object> keys = cachedObjects.keySet(); 
       final long now = System.currentTimeMillis(); 
       keys.forEach(k -> { 

         { 
          ObjectWrapper ow = cachedObjects.get(k); 
          if(ow.getExpireTime() < now) 
          { 
           int size = cachedObjects.size(); 
           cachedObjects.remove(k, ow); 
           System.out.println("DEL:" + k + ", from " + size + " to " + cachedObjects.size()); 
          } 
         } 
       }); 
      } 
      catch (Throwable t) 
      { 
       t.printStackTrace(); 
      } 
     } 
    }; 

    executor.scheduleWithFixedDelay(task, 5, 15, TimeUnit.SECONDS); 
} 

당신은 스택 트레이스를 얻을 수 있습니다

private Cache() 
{ 
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); 

    Runnable task =() -> { 
     synchronized(LOCK) 
     { 
      try 
      { 
       System.out.println("Cleanup"); 
       final long now = System.currentTimeMillis(); 
       final Iterator<Map.Entry<Object, ObjectWrapper>> iter = cachedObjects.entrySet().iterator(); 
       while (iter.hasNext()) { 
        final Map.Entry<Object, ObjectWrapper> entry = iter.next(); 
        final ObjectWrapper ow = entry.getValue(); 
        if(ow.getExpireTime() < now) 
        { 
         final Object k = entry.getKey(); 
         int size = cachedObjects.size(); 
         iter.remove(); 
         System.out.println("DEL:" + k + ", from " + size + " to " + cachedObjects.size()); 
        } 
       } 
      } 
      catch (Throwable t) 
      { 
       t.printStackTrace(); 
      } 
     } 
    }; 

    executor.scheduleWithFixedDelay(task, 5, 15, TimeUnit.SECONDS); 
} 

수행 예상 출력을 얻을 수 있습니다.

Cleanup 
Adding id:1 
Adding id:2 
Cleanup 
Test: 
1>1467228864763::1467228909763 
2>1467228864763::1467228919764 
Adding id:3 
Test: 
1>1467228874766::1467228909763 
2>1467228874766::1467228919764 
3>1467228874766::1467228929764 
Cleanup 
Adding id:4 
Test: 
1>1467228884766::1467228909763 
2>1467228884766::1467228919764 
3>1467228884766::1467228929764 
4>1467228884766::1467228939764 
Adding id:5 
Cleanup 
Test: 
1>1467228894770::1467228909763 
2>1467228894770::1467228919764 
3>1467228894770::1467228929764 
4>1467228894770::1467228939764 
5>1467228894770::1467228949765 
Adding id:6 
Test: 
1>1467228904771::1467228909763 
2>1467228904771::1467228919764 
3>1467228904771::1467228929764 
4>1467228904771::1467228939764 
5>1467228904771::1467228949765 
6>1467228904771::1467228959765 
Cleanup 
Adding id:7 
Test: 
1>1467228914771::1467228909763 
2>1467228914771::1467228919764 
3>1467228914771::1467228929764 
4>1467228914771::1467228939764 
5>1467228914771::1467228949765 
6>1467228914771::1467228959765 
7>1467228914771::1467228969765 
Adding id:8 
Cleanup 
DEL:1, from 8 to 7 
DEL:2, from 7 to 6 
Test: 
3>1467228924772::1467228929764 
4>1467228924772::1467228939764 
5>1467228924772::1467228949765 
6>1467228924772::1467228959765 
7>1467228924772::1467228969765 
8>1467228924772::1467228979765 
Adding id:9 
Test: 
3>1467228934772::1467228929764 
4>1467228934772::1467228939764 
5>1467228934772::1467228949765 
6>1467228934772::1467228959765 
7>1467228934772::1467228969765 
8>1467228934772::1467228979765 
9>1467228934772::1467228989766 
Cleanup 
DEL:3, from 7 to 6 
Adding id:10 
Test: 
4>1467228944773::1467228939764 
5>1467228944773::1467228949765 
6>1467228944773::1467228959765 
7>1467228944773::1467228969765 
8>1467228944773::1467228979765 
9>1467228944773::1467228989766 
10>1467228944773::1467228999766 
Adding id:11 
Cleanup 
DEL:4, from 8 to 7 
DEL:5, from 7 to 6 
Test: 
6>1467228954773::1467228959765 
7>1467228954773::1467228969765 
8>1467228954773::1467228979765 
9>1467228954773::1467228989766 
10>1467228954773::1467228999766 
11>1467228954773::1467229009766 
Adding id:12 
Test: 
6>1467228964774::1467228959765 
7>1467228964774::1467228969765 
8>1467228964774::1467228979765 
9>1467228964774::1467228989766 
10>1467228964774::1467228999766 
11>1467228964774::1467229009766 
12>1467228964774::1467229019767 
Cleanup 
DEL:6, from 7 to 6 
Adding id:13 
+0

키 반복자를 잡아낼 생각이 없었습니다. 원본 컬렉션에 영향을 줄 수있는 모든 변경 사항에서 제거 된 새 객체입니다. – supertorqued