2016-07-26 1 views
0

내 프로젝트에 필요한 Runnable 큐를 관리하려고 간단한 TaskManager을 만들었습니다. 그러나 간단한 시나리오에서 새로운 Runnable을 추가하면 호출 스레드 (기본 UI 스레드)가 차단됩니다.동기화, 잠금 및 대기 메인 UI 스레드

현재 작업이 완료되지 않은 상태에서 새 작업을 추가하면 이런 일이 발생합니다. 아래에서 재생산 시나리오를 볼 수 있습니다. 이유를 분명히 이해하지 못하고 어떻게 예방할 수 있습니까?

public class RunnableWithCompl implements Runnable { 

    private CompletionHandler completionHandler; 
    private Runnable runnable; 

    public RunnableWithCompl(Runnable runnable, CompletionHandler completionHandler) { 
     this.runnable = runnable; 
     this.completionHandler = completionHandler; 
    } 

    @Override 
    public void run() { 
     runnable.run(); 
     if(completionHandler != null) 
      completionHandler.onFinish(); 
    } 
} 

그리고 CompletionHandler 인터페이스 :

여기
public class TaskManager { 

    private Queue<Runnable> executionQueue; 
    private final Object lock = new Object(); 

    public TaskManager() { 
     executionQueue = new LinkedList<>(); 
     startListening(); 
    } 

    public void executeAsyncWithCompl(Runnable runnable, CompletionHandler completionHandler) { 
     Runnable runnableWithCompl = new RunnableWithCompl(runnable, completionHandler); 
     executeRunnable(runnableWithCompl); 
    } 

    private void executeRunnable(Runnable runnable) { 
     synchronized (lock) { 
      executionQueue.add(runnable); 
      lock.notifyAll(); 
     } 
    } 

    public void release() { 
     synchronized (lock) { 
      lock.notify(); 
     } 
    } 

    private void startListening() { 
     Thread executionThread = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       listenTasks(); 
      } 
     }); 
     executionThread.start(); 
    } 

    private void listenTasks() { 
     synchronized (lock) { 
      while (true) { 
       try { 
        if(executionQueue.isEmpty()) { 
         lock.wait(); 
        } 
        Runnable runnable = executionQueue.poll(); 
        runnable.run(); 
       } catch (InterruptedException ie) { 
        ie.printStackTrace(); 
       } 
      } 
     } 
    } 
} 

는 RunnableWithCompl 클래스입니다 :

public interface CompletionHandler { 
    void onFinish(); 
} 

시나리오

는 작업 관리자 클래스입니다. 회 전자 (UI가 차단되지 않음을 나타냄) 및 긴 작업을 트리거하는 버튼이있는 활동이 있다고 가정 해 보겠습니다.

private TaskManager taskManager; 

public void init() { 
    taskManager = new TaskManager(); 
    launchLongTask(); 
} 

private void onButtonClick() { 
     launchLongTask() ; 
} 

private void launchLongTask() { 
    Runnable longTask = new Runnable() { 
      @Override 
      public void run() { 
       try { 
        Thread.sleep(15000); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
      } 
     }; 

     Log.d(TAG, "Launching long task"); 

     taskManager.executeAsyncWithCompl(longTask, new CompletionHandler() { 
      @Override 
      public void onFinish() { 
       Log.d(TAG, "Long task finished"); 
      } 
     }); 
} 
+0

당신은'java.util.concurrent'에서 Executor 클래스의 바퀴를 재발 명하고있는 것처럼 보입니다 –

답변

1

문제는 startListening() 구현에 있습니다. 모니터는 작업을 수행하는 동안 모니터를 lock으로 유지합니다. 이는 작업을 수행하는 동안 다른 방법으로 모니터를 얻을 수 없음을 의미합니다. 이것은 대기 행렬이 더 이상 없을 때까지 release()executeRunnable(...)이 차단됨을 의미합니다.

startListening()을 실행하는 스레드가 모니터를 해제 할 때까지 스레드를 계속할 수 없으므로 스레드가 다른 스레드보다 먼저 통지를 받으면 스레드가 차단 될 수 있음을 의미합니다.

+0

고맙습니다, 제 생각에는 당신이 내 문제를 해결했다고 생각합니다! if (executionQueue.isEmpty() 주위의 동기화 된 블록을 listenTasks 내에서 이동하여 작업이 실행되는 동안 모니터를 유지하지 않으면 작동 함) 좋은 해결책입니까? 다시 감사합니다. – Xys

+0

@Xys 예, 'runnable'이 나중에 null인지 확인해야하지만, 뭔가가 큐에 무엇인가 넣지 않고'release()'를 호출하면 아무 것도 반환하지 않을 것입니다. – Kiskae