2017-09-12 2 views
-2

내 서비스에 대한 REST 호출이 새 스레드를 작성하는 백엔드 서비스를 구축 중입니다. 쓰레드가 죽을 때까지 5 분을 기다려서 아무 것도받지 못하면 쓰레드는 다른 REST 호출을 기다린다. 모든 스레드를 추적하려면 현재 실행중인 모든 스레드를 추적하는 컬렉션이 있으므로 REST 호출이 마침내 사용자가 동의하거나 거부 할 때와 같이 사용자 ID를 사용하여 해당 스레드를 식별 할 수 있습니다. 거부 된 경우 해당 스레드가 수락되어 다음 작업을 수행 할 수있는 경우 해당 스레드를 컬렉션에서 제거합니다. 동시성 문제를 피하기 위해 ConcurrentMap을 사용하여 구현했습니다.ConcurrentMap에 스레드를 저장하는 것이 안전합니까?

처음으로 스레드로 작업하기 때문에 발생할 수있는 문제가 간과되지 않도록하고 싶습니다. 제 코드를 살펴보고 더 잘할 수 있는지 또는 결함이 있는지 알려주세요.

public class UserAction extends Thread { 

    int  userID; 
    boolean isAccepted = false; 
    boolean isDeclined = false; 
    long timeNow  = System.currentTimeMillis(); 
    long timeElapsed = timeNow + 50000; 

    public UserAction(int userID) { 
     this.userID = userID; 
    } 

    public void declineJob() { 
     this.isDeclined = true; 
    } 

    public void acceptJob() { 
     this.isAccepted = true; 
    } 

    public boolean waitForApproval(){ 
     while (System.currentTimeMillis() < timeElapsed){ 
     System.out.println("waiting for approval"); 
     if (isAccepted) { 
      return true; 
     } else if (declined) { 
      return false; 
     } 

    } 
     return isAccepted; 
    } 

    @Override 
    public void run() { 
     if (!waitForApproval) { 
      // mustve timed out or user declined so remove from list and return thread immediately 
      tCollection.remove(userID); 
      // end the thread here 
      return; 
     } 
     // mustve been accepted so continue working 
    } 
} 

public class Controller { 

    public static ConcurrentHashMap<Integer, Thread> tCollection = new ConcurrentHashMap<>(); 

    public static void main(String[] args) { 
     int barberID1 = 1; 
     int barberID2 = 2; 

     tCollection.put(barberID1, new UserAction(barberID1)); 
     tCollection.put(barberID2, new UserAction(barberID2)); 

     tCollection.get(barberID1).start(); 
     tCollection.get(barberID2).start(); 

     Thread.sleep(1000); 
     // simulate REST call accepting/declining job after 1 second. Usually this would be in a spring mvc RESTcontroller in a different class. 
     tCollection.get(barberID1).acceptJob(); 
     tCollection.get(barberID2).declineJob(); 

    } 
} 
+1

'스레드 '를 확장하지 마십시오. 대신에'Runnable' 또는'Callable'을 구현하십시오. ExecutorService를 사용하여 실행하십시오. 당신은 방금 스레드를 사용하기 시작한 사람에게 너무 영리 해 지려고합니다. 'CompletableFuture'도 여기에 적합 할 수 있습니다. – Kayaman

+0

코드가 어떻게 되었습니까? 컴파일 할 수 없습니다 (예 : '공공'은 없다. 입력 보조자가 자동 ​​수정을 수행하는 휴대 전화에 입력 한 적이 있습니까? – Lothar

+0

@Kayaman Yea 나는 물건을 더 복잡하게 만들고 있다고 나는 동의한다. ExecutorService를 사용하려고 생각했지만 나중에 스레드를 참조 할 수있게되어 작업을 수락 할 수 있습니까? – Claudiga

답변

1

여기에는 (명시 적) 스레드가 필요하지 않습니다. 첫 번째 나머지 호출에서 생성되는 작업 개체의 공유 풀.

두 번째 나머지 호출이 오면 이미 사용할 스레드 (나머지 호출을 처리하는 스레드)가 있습니다. 사용자 ID에 따라 타스크 오브젝트를 검색하면됩니다. 또한 만료 된 작업을 제거해야합니다 (예 : DelayQueue).

의사 코드 :

public void rest1(User u) { 
    UserTask ut = new UserTask(u); 
    pool.put(u.getId(), ut); 
    delayPool.put(ut); // Assuming UserTask implements Delayed with a 5 minute delay 
} 

public void rest2(User u, Action a) { 
    UserTask ut = pool.get(u.getId()); 
    if(!a.isAccepted() || ut == null) 
     pool.remove(u.getId()); 
    else 
     process(ut); 

    // Clean up the pool from any expired tasks, can also be done in the beginning 
    // of the method, if you want to make sure that expired actions aren't performed 
    while((UserTask u = delayPool.poll()) != null) 
     pool.remove(u.getId()); 
} 
+0

당신은 당연히 DelayQueue가 작업을 수행하는 첫 번째 위치에서 스레드를 명시 적으로 사용할 필요가 없었습니다. 감사 – Claudiga

1

당신이 당신의 깃발 isAccepted 및 클래스 AtomicBooleanisDeclined을해야 동기화 문제가있다. 중요한 개념은 한 스레드의 메모리 변경이 해당 데이터를 필요로하는 다른 스레드와 통신하도록하는 조치를 취해야한다는 것입니다. 이들은 메모리 펜스 (fence)라고 불리며, 종종 동기화 호출간에 암묵적으로 발생합니다.

'중앙 메모리'가있는 (단순한) 폰 노이만 아키텍처의 아이디어는 대부분의 최신 기계에서는 거짓이며 데이터가 캐시/스레드간에 올바르게 공유된다는 것을 알아야합니다.

다른 사람들도 제안한 것처럼 각 작업에 대한 스레드를 만드는 것은 좋지 않은 모델입니다. 너무 많은 작업을 제출할 경우 응용 프로그램이 너무 크게 늘어나서 응용 프로그램이 취약한 상태로 빠져 나갈 수 있습니다. 메모리에는 몇 가지 제한이 있으므로 한 번에 여러 개의 보류중인 작업 만있을 수 있지만 스레드의 천장은 훨씬 낮습니다. 당신이 스핀 기다리고 있기 때문에 그건 더 나빠질 것입니다. 스핀 대기는 상태를 기다리는 루프에 스레드를 넣습니다. 더 나은 모델은 ConditionVariable을 기다리므로 기다리는 것 외에는 아무 것도하지 않는 스레드는 기다리고있는 것이 준비가되었다는 것을 알릴 때까지 운영 체제에 의해 일시 중지 될 수 있습니다.

종종 스레드를 생성하고 소멸시키는 데 시간과 리소스에 상당한 오버 헤드가 발생합니다. 대부분의 플랫폼은 동시에 상대적으로 적은 수의 스레드 만 실행하여 '비싼'스레드를 많이 만들어 스누핑 된 (일시 중단 된) 시간을 대부분 비효율적으로 보내는 데 사용할 수 있습니다.

올바른 모델은 고정 된 수의 스레드 (또는 상대적으로 고정 된 수)의 풀을 실행하고 스레드가 '걸리는'공유 큐에 작업을 배치하고 처리합니다.

해당 모델은 일반적으로 "스레드 풀"로 알려져 있습니다. 보아야 할 엔트리 레벨 구현은 ThreadPoolExecutor입니다. https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html

관련 문제