1. 개요

이 기사에서는 특정 시간 후에 장기 실행을 종료하는 방법에 대해 알아 봅니다. 이 문제에 대한 다양한 솔루션을 탐색 할 것입니다. 또한 우리는 그들의 함정 중 일부를 다룰 것입니다.

2. 루프 사용

전자 상거래 응용 프로그램에서 제품 항목의 일부 세부 정보와 같이 여러 항목을 루프로 처리하고 있지만 모든 항목을 완료 할 필요는 없을 수 있다고 가정 해보십시오.

사실, 우리는 특정 시간까지만 처리하고 싶고 그 후에는 실행을 중지하고 그 시간까지 List이 처리 한 내용을 표시하려고합니다.

간단한 예를 보겠습니다.

long start = System.currentTimeMillis();
long end = start + 30*1000;
while (System.currentTimeMillis() < end) {
    // Some expensive operation on the item. 
}

여기에서 시간이 30 초 제한을 초과하면 루프가 중단됩니다. 위의 솔루션에는 몇 가지 주목할만한 점이 있습니다.

  • 낮은 정확도 : 루프가 부과 된 시간 제한보다 오래 실행될 수 있습니다 . 이는 각 반복에 걸리는 시간에 따라 다릅니다. 예를 들어 각 반복에 최대 7 초가 소요될 수있는 경우 총 시간은 최대 35 초가 될 수 있으며, 이는 원하는 시간 제한 인 30 초보다 약 17 % 더 길어집니다.
  • 차단 : 메인 스레드에서의 이러한 처리는 오랫동안 차단 될 수 있으므로 좋은 생각이 아닐 수 있습니다 . 대신, 이러한 작업은 주 스레드에서 분리되어야합니다.

다음 섹션에서는 인터럽트 기반 접근 방식이 이러한 제한을 제거하는 방법에 대해 설명합니다.

3. 인터럽트 메커니즘 사용

여기서는 장기 실행 작업을 수행하기 위해 별도의 스레드를 사용합니다. 주 스레드는 시간 초과시 작업자 스레드에 인터럽트 신호를 보냅니다.

작업자 스레드가 아직 살아 있으면 신호를 포착하고 실행을 중지합니다. 작업자가 시간 초과 전에 완료되면 작업자 스레드에 영향을주지 않습니다.

작업자 스레드를 살펴 보겠습니다.

class LongRunningTask implements Runnable {
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            // log error
        }
    }
}

여기서 Thread.sleep 은 장기 실행 작업을 시뮬레이션합니다. 대신 다른 작업이있을 수 있습니다. 모든 작업이 인터럽트 가능한 것은 아니므로 인터럽트 플래그확인하는 것이 중요합니다 . 따라서 이러한 경우 수동으로 플래그를 확인해야합니다.

또한 모든 반복에서이 플래그를 확인하여 스레드가 최대 한 번의 반복 지연 내에 자체 실행을 중지하는지 확인해야합니다.

다음으로 인터럽트 신호를 보내는 세 가지 메커니즘에 대해 설명합니다.

3.1. 타이머 사용

또는 TimerTask만들어 시간 초과시 작업자 스레드를 중단 할 수 있습니다 .

class TimeOutTask extends TimerTask {
    private Thread t;
    private Timer timer;

    TimeOutTask(Thread t, Timer timer){
        this.t = t;
        this.timer = timer;
    }
 
    public void run() {
        if (t != null && t.isAlive()) {
            t.interrupt();
            timer.cancel();
        }
    }
}

여기서 우리는 생성시 작업자 스레드를 받는 TimerTask정의했습니다 . 그것은 것 그것의 호출에 작업자 스레드를 중단 실행 방법 . 타이머  트리거됩니다 TimerTask를  지정된 지연 후를 :

Thread t = new Thread(new LongRunningTask());
Timer timer = new Timer();
timer.schedule(new TimeOutTask(t, timer), 30*1000);
t.start();

3.2. Future # get 메소드 사용

Timer 를 사용하는 대신 Futureget 메소드를 사용할 수도 있습니다 .

ExecutorService executor = Executors.newSingleThreadExecutor();
Future future = executor.submit(new LongRunningTask());
try {
    f.get(30, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    f.cancel(true);
} finally {
    service.shutdownNow();
}

여기서는 ExecutorService사용하여 Future 인스턴스를 반환하는 작업자 스레드를 제출했으며 get 메서드는 지정된 시간까지 주 스레드를 차단합니다. 그것은 올릴 것이다 TimeoutException을 지정된 제한 시간 후. 에서 캐치 블록, 우리는이 호출하여 작업자 스레드를 중단하는 취소 온 방법을 F의 uture의 객체입니다.

이전 접근 방식에 비해이 접근 방식의 주요 이점은 풀을 사용하여 스레드를 관리하는 반면 Timer 는 단일 스레드 (풀 없음) 만 사용한다는 것 입니다.

3.3. ScheduledExcecutorSercvice 사용

ScheduledExecutorService사용 하여 작업을 중단 할 수도 있습니다 . 이 클래스는 ExecutorService 의 확장 이며 실행 예약을 처리하는 여러 메서드를 추가하여 동일한 기능을 제공합니다. 이것은 설정된 시간 단위의 특정 지연 후에 주어진 작업을 실행할 수 있습니다.

ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Future future = executor.submit(new LongRunningTask());
executor.schedule(new Runnable(){
    public void run(){
        future.cancel(true);
    }
}, 1000, TimeUnit.MILLISECONDS);
executor.shutdown();

여기서 우리는 newScheduledThreadPool 메소드를 사용하여 크기가 2 인 예약 된 스레드 풀을 생성했습니다 . ScheduledExecutorService를 # 1 예약  방법은 소요 의 Runnable , 지연 값 및 지연 유닛.

위 프로그램은 제출 시점으로부터 1 초 후에 실행할 작업을 예약합니다. 이 작업은 원래의 장기 실행 작업을 취소합니다.

이전 접근 방식과 달리 Future # get 메서드를 호출하여 기본 스레드를 차단하지 않습니다 . 따라서 위에서 언급 한 모든 접근 방식 중에서 가장 선호되는 접근 방식 입니다.

4. 보증이 있습니까?

특정 시간이 지나면 실행이 중지된다는 보장은 없습니다 . 주된 이유는 모든 차단 방법이 인터럽트 가능한 것은 아니기 때문입니다. 실제로 인터럽트 가능한 잘 정의 된 메서드는 몇 개뿐입니다. 따라서 스레드가 중단되고 플래그가 설정되면 이러한 중단 가능한 메서드 중 하나에 도달 할 때까지 다른 작업이 발생하지 않습니다 .

예를 들어 읽기  및 쓰기 메서드는 InterruptibleChannel로 생성 된 스트림에서 호출되는 경우에만 인터럽트 할 수 있습니다. BufferedReaderInterruptibleChannel 이 아닙니다 . 따라서 스레드가 파일을 읽는 데 사용하는 경우 read 메서드 에서 차단 된이 스레드에서 interrupt ()호출 해도 효과가 없습니다.

그러나 루프에서 읽을 때마다 인터럽트 플래그를 명시 적으로 확인할 수 있습니다. 이것은 약간의 지연으로 스레드를 중지 할 수있는 합리적인 보증을 제공합니다. 그러나 읽기 작업에 얼마나 많은 시간이 걸릴 수 있는지 알 수 없기 때문에 엄격한 시간 후에 스레드가 중지되는 것은 아닙니다.

반면에 Object 클래스 wait 메소드 는 인터럽트 가능합니다. 따라서 wait 메소드 에서 차단 된 스레드 는 인터럽트 플래그가 설정된 후 즉시 InterruptedException 을 발생시킵니다.

메서드 시그니처에서 throws InterruptedException 을 찾아 차단 메서드를 식별 할 수 있습니다 .

한 가지 중요한 조언은 더 이상 사용되지 않는 Thread.stop () 메서드를 사용하지 않는 것입니다. 스레드를 중지하면 잠긴 모든 모니터의 잠금이 해제됩니다. 이것은 스택 위로 전파 되는 ThreadDeath 예외로 인해 발생합니다 .

이전에 이러한 모니터에 의해 보호 된 개체가 일관성없는 상태에있는 경우 일관성없는 개체가 다른 스레드에 표시됩니다. 이것은 감지하고 추론하기 매우 어려운 랜덤의 행동으로 이어질 수 있습니다.

5. 결론

이 예제에서는 각각의 장단점과 함께 주어진 시간이 지나면 실행을 중지하는 다양한 기술을 배웠습니다. 전체 소스 코드는 GitHub 에서 찾을 수 있습니다 .