>  기사  >  Java  >  Java 스레드의 비정상 종료 문제를 해결하는 방법

Java 스레드의 비정상 종료 문제를 해결하는 방법

清浅
清浅원래의
2019-04-25 14:18:295806검색

Java에서 스레드의 비정상적인 끝을 해결하는 방법은 다음과 같습니다. 먼저 스레드 실행 중에 생성된 예외를 캡처한 다음 스레드가 비정상일 때 setUncaughtExceptionHandler 메서드를 호출하여 예외를 포착하고 최종적으로 해결합니다.

Java 스레드의 비정상 종료 문제를 해결하는 방법

【추천 튜토리얼: Java 튜토리얼

스레드를 사용할 때 다음과 같은 시나리오가 있을 수 있습니다.

(1) 함께. 이 사업은 상대적으로 시간이 많이 걸리는 작업이 생성되며 이 사업의 반환은 작업을 기다릴 필요가 없습니다. 그런 다음 이 비동기 작업을 완료하기 위해 스레드를 시작하는 경우가 많습니다.

(2) 정기적으로 데이터 지우기와 같은 예약된 작업이 필요합니다. 작업을 수행하려면 예약된 실행 스레드를 시작합니다.

위 문제는 비교적 간단하니 새 스레드를 만들어서 해보세요. 하지만 우리는 종종 문제를 간과하는데, 스레드가 비정상이라면 어떻게 해야 할까요? 예를 들어, 시간이 많이 걸리는 작업의 절반만 완료하면 비정상적으로 종료됩니다(여기에서는 트랜잭션 일관성이 고려되지 않고 작업이 완료되어야 한다는 것만 고려합니다). 또 다른 예는 데이터를 지울 때 데이터베이스 연결이 끊어지는 것입니다. 이때 스레드가 종료되고 작업이 종료되었음을 알 수 있습니다. 예약된 작업을 시작하려면 전체 프로젝트를 다시 시작해야 합니다.

이러한 문제를 해결하는 열쇠는 스레드 실행 중에 발생하는 예외를 어떻게 캡처하느냐 하는 것입니다.

JDK API를 살펴보면 Thread에 setUncaughtExceptionHandler 메서드가 있다는 것을 알 수 있는데, 이를 통해 스레드에서 예외가 발생할 때 이 메서드를 호출할 수 있습니다.

Java에서 비정상적인 스레드 종료에 대한 해결책은 다음과 같습니다.

시나리오 1 해결책:

public class Plan1 {
    
    private SimpleTask task = new SimpleTask();
    
    public static void main(String[] args) {
        Plan1 plan = new Plan1();
        plan.start();
    }
    public void start(){
        Thread thread = new Thread(task);
        //thread.setDaemon(true); //注释调 否则看不到输出
        thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(e.getMessage());
                start();
            }
        });
        thread.start();
    }
    
    class SimpleTask implements Runnable{
        private int task = 10;
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+"--"+"启动");
            while(task>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(System.currentTimeMillis()%3==0){
                    throw new RuntimeException("模拟异常");
                }
                System.out.println(threadName+"--"+"执行task"+task);
                task--;
            }
            System.out.println(threadName+"--"+"正常终止");
        }
    }
}

결과 출력:

Thread-0--启动
Thread-0--执行task10
Thread-0--执行task9
Thread-0--执行task8
Thread-0--执行task7
模拟异常
Thread-1--启动
Thread-1--执行task6
Thread-1--执行task5
模拟异常
Thread-2--启动
Thread-2--执行task4
Thread-2--执行task3
模拟异常
Thread-3--启动
Thread-3--执行task2
模拟异常
Thread-4--启动
Thread-4--执行task1
Thread-4--正常终止

아직 시나리오 1에서 스레드 풀 방법, 아이디어를 살펴보겠습니다. 동일합니다. 왜 또 다른 단일 스레드 풀 메소드를 작성합니까?

public class Plan3 {
    private SimpleTask task = new SimpleTask();
    private MyFactory factory = new MyFactory(task);
    public static void main(String[] args) {
        Plan3 plan = new Plan3();
        ExecutorService pool = Executors.newSingleThreadExecutor(plan.factory);
        pool.execute(plan.task);
        pool.shutdown();
    }
    
    class MyFactory implements ThreadFactory{
        private SimpleTask task;
        public MyFactory(SimpleTask task) {
            super();
            this.task = task;
        }
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
                @Override
                public void uncaughtException(Thread t, Throwable e) {
                    ExecutorService pool = Executors.newSingleThreadExecutor(new MyFactory(task));
                    pool.execute(task);
                    pool.shutdown();
                }
            });
            return thread;
        }
    }
    
    class SimpleTask implements Runnable{
        private int task = 10;
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+"--"+"启动");
            while(task>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(System.currentTimeMillis()%3==0){
                    throw new RuntimeException("模拟异常");
                }
                System.out.println(threadName+"--"+"执行task"+task);
                task--;
            }
            System.out.println(threadName+"--"+"正常终止");
        }
    }
}

결과 출력:

Thread-0--启动
Thread-0--执行task10
Thread-0--执行task9
Thread-1--启动
Thread-1--执行task8
Thread-2--启动
Thread-2--执行task7
Thread-2--执行task6
Thread-2--执行task5
Thread-2--执行task4
Thread-2--执行task3
Thread-2--执行task2
Thread-3--启动
Thread-3--执行task1
Thread-3--正常终止

여기서는 단일 스레드만 사용했기 때문에 위와 큰 차이가 없다는 것을 알았습니다. 그러나 스레드 풀이 스레드 예외를 포착하는 방법도 보여줍니다.

시나리오 2에 대한 솔루션

이제 시나리오 2의 예약된 작업을 살펴보겠습니다. 단일 스레드 풀 예외 잡기 방법을 작성하려는 이유는 다음과 같습니다.

우리는 예약된 작업을 위해 ScheduledExecutorService를 자주 사용하는데, 이는 위의 ExecutorService 획득 방법과 동일합니다. 그러나 위의 방법을 따라 예약된 작업을 작성하면 예외가 발생합니다. uncaughtException 메소드에서는 스레드 예외를 얻을 수 없다는 것을 알게 될 것입니다. 예외가 사라지거나, 스레드에서 예외가 발생하면 uncaughtException 메서드가 전혀 호출되지 않습니다. 나중에 관련 API를 확인한 결과 ScheduledExecutorService에서 예외를 얻는 방법은 ScheduledFuture 개체를 사용하여 얻을 수 있음을 발견했습니다. 구체적인 방법은 다음과 같습니다.

public class Plan2 {
    private SimpleTask task = new SimpleTask();
    public static void main(String[] args) {
        Plan2 plan = new Plan2();
        start(plan.task);
    }
    
    public static void start(SimpleTask task){
        ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
        ScheduledFuture<?> future = pool.scheduleAtFixedRate(task, 0, 1000, TimeUnit.MILLISECONDS);
        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            System.out.println(e.getMessage());
            start(task);
        }finally {
            pool.shutdown();
        }
    }
    
    class SimpleTask implements Runnable{
        private volatile int count = 0;
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+"--"+"启动");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(System.currentTimeMillis()%3==0){
                throw new RuntimeException("模拟异常");
            }
            System.out.println(threadName+"--"+"执行task"+count);
            count++;
            System.out.println(threadName+"--"+"正常终止");
        }
    }
}

결과 출력:

pool-1-thread-1--启动
java.lang.RuntimeException: 模拟异常
pool-2-thread-1--启动
pool-2-thread-1--执行task0
pool-2-thread-1--正常终止
pool-2-thread-1--启动
pool-2-thread-1--执行task1
pool-2-thread-1--正常终止
pool-2-thread-1--启动
pool-2-thread-1--执行task2
pool-2-thread-1--正常终止
pool-2-thread-1--启动
java.lang.RuntimeException: 模拟异常
pool-3-thread-1--启动
pool-3-thread-1--执行task3
pool-3-thread-1--正常终止
pool-3-thread-1--启动
java.lang.RuntimeException: 模拟异常
pool-4-thread-1--启动
pool-4-thread-1--执行task4
pool-4-thread-1--正常终止
.....

지금까지 우리는 예약된 작업에서 예외가 발생하면 항상 이를 실행할 스레드가 있습니다. 하나의 스레드가 떨어지면 후속 스레드가 대신합니다.

위 내용은 Java 스레드의 비정상 종료 문제를 해결하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.