스레드를 종료하는 방법을 묻는 질문에 아마도 대부분의 사람들은 Thread.stop 메서드를 호출할 수 있다는 것을 알고 있을 것입니다.
하지만 이 방법은 jdk1.2 이후에는 더 이상 권장되지 않습니다. 왜 권장되지 않습니까?
먼저 이 메서드의 정의를 살펴보겠습니다.
@Deprecated(since="1.2") public final void stop() { @SuppressWarnings("removal") SecurityManager security = System.getSecurityManager(); if (security != null) { checkAccess(); if (this != Thread.currentThread()) { security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION); } } // A zero status value corresponds to "NEW", it can't change to // not-NEW because we hold the lock. if (threadStatus != 0) { resume(); // Wake up thread if it was suspended; no-op otherwise } // The VM can handle all thread states stop0(new ThreadDeath()); }
코드에서 stop 메서드가 먼저 스레드 액세스 권한이 있는지 확인하는 것을 볼 수 있습니다. 권한이 있는 경우 현재 스레드가 새로 생성된 스레드인지 확인합니다. 권한이 없으면 재개 메서드를 호출하여 스레드의 일시 중지 상태를 해제합니다.
마지막으로 stop0 메소드를 호출하여 스레드를 종료합니다.
Resume과 stop0은 두 가지 기본 메서드이므로 여기서는 구체적인 구현에 대해 논의하지 않습니다.
정지 방법이 합리적이고 문제가 없는 것 같습니다. 그렇다면 이 방법이 왜 안전하지 않습니까?
다음 예를 살펴보겠습니다.
우리는 NumberCounter 클래스를 생성합니다. 이 클래스에는 숫자에 1을 추가하는 데 사용되는 lossNumber라는 안전한 메서드가 있습니다.
public class NumberCounter { //要保存的数字 private volatile int number=0; //数字计数器的逻辑是否完整 private volatile boolean flag = false; public synchronized int increaseNumber() throws InterruptedException { if(flag){ //逻辑不完整 throw new RuntimeException("逻辑不完整,数字计数器未执行完毕"); } //开始执行逻辑 flag = true; //do something Thread.sleep(5000); number++; //执行完毕 flag=false; return number; } }
실제로 이러한 메서드는 실행하는 데 시간이 오래 걸릴 수 있으므로 여기서는 Call Thread.sleep을 전달하여 이 시간 소모적인 작업을 시뮬레이션합니다.
여기에는 raiseNumber 메소드가 성공적으로 실행되었는지 여부를 표시하는 플래그 매개변수도 있습니다.
좋아요, 다음으로 스레드에서 이 클래스의 메서드를 호출하고 무슨 일이 일어나는지 확인합니다.
public static void main(String[] args) throws InterruptedException { NumberCounter numberCounter= new NumberCounter(); Thread thread = new Thread(()->{ while (true){ try { numberCounter.increaseNumber(); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); Thread.sleep(3000); thread.stop(); numberCounter.increaseNumber(); }
여기서 스레드를 만들고 이 스레드가 3초 동안 실행될 때까지 기다린 다음 thread.stop 메서드를 직접 호출합니다. 결과적으로 다음 예외가 발생했음을 발견했습니다.
스레드 "main" java.lang.RuntimeException의 예외: 논리가 불완전하고
com.flydean.NumberCounter.increaseNumber(NumberCounter에서 숫자 카운터가 실행되지 않았습니다. .java:12)
at com.flydean.Main.main(Main.java:18)
이는 thread.stop 메소드가 스레드 실행을 직접 종료하여 mberCounter.increaseNumber가 완료되지 않기 때문입니다.
하지만 이 미완성 상태는 숨겨져 있습니다. thread.stop 메소드를 사용하여 스레드를 종료하면 알 수 없는 결과가 발생할 가능성이 높습니다.
그래서 우리는 thread.stop이 안전하지 않다고 말합니다.
그럼 thread.stop 메소드가 호출되지 않으면 어떻게 스레드를 안전하게 종료할 수 있을까요?
소위 안전이란 스레드의 논리가 절반만 실행되는 것이 아니라 완전히 실행되어야 함을 의미합니다.
이 효과를 달성하기 위해 Thread는 세 가지 유사한 메서드인 Interrupt, Interrupted 및 isInterrupted를 제공합니다.
interrupt는 스레드에 대한 인터럽트 플래그를 설정하는 것입니다. 중단된 것은 인터럽트를 감지하고 인터럽트 상태를 지우는 것입니다. isInterrupted는 인터럽트만 감지합니다. 또 다른 중요한 점은 Interrupted가 현재 스레드에서 작동하는 클래스 메소드이고 isInterrupted가 이 스레드, 즉 코드에서 이 메소드를 호출하는 인스턴스가 나타내는 스레드에서 작동한다는 것입니다.
interrupt는 인터럽트 메서드입니다. 해당 작업 흐름은 다음과 같습니다.
현재 스레드 인스턴스가 Object 클래스 또는 Join()의 wait(), wait(long) 또는 wait(long, int) 메서드를 호출하는 경우 , Join(long), Join(long, int) 메서드 또는 Thread.sleep(long) 또는 Thread.sleep(long, int) 메서드가 이 인스턴스에서 호출되어 차단 상태에 있는 경우 인터럽트 상태는 다음과 같습니다. 지워지고 InterruptedException을 받게 됩니다.
InterruptibleChannel에서 I/O 작업 중에 이 스레드가 차단되면 채널이 닫히고 스레드의 인터럽트 상태가 true로 설정되며 스레드는 java.nio.channels .ClosedByInterruptException 예외를 수신합니다.
이 스레드가 java.nio.channels.Selector에서 차단되면 스레드의 인터럽트 상태가 true로 설정되고 선택 작업에서 즉시 반환됩니다.
위의 상황 중 어느 것도 해당되지 않으면 인터럽트 상태를 true로 설정하세요.
위 예제에서는 NumberCounter의 증가Number 메소드에서 Thread.sleep 메소드를 호출했기 때문에 이때 스레드의 인터럽트 메소드가 호출되면 스레드는 InterruptedException을 발생시킵니다.
위의 호출 예를 다음과 같이 변경합니다.
public static void main(String[] args) throws InterruptedException { NumberCounter numberCounter = new NumberCounter(); Thread thread = new Thread(() -> { while (true) { try { numberCounter.increaseNumber(); } catch (InterruptedException e) { System.out.println("捕获InterruptedException"); throw new RuntimeException(e); } } }); thread.start(); Thread.sleep(500); thread.interrupt(); numberCounter.increaseNumber(); }
실행 후 다시 시도:
Exception in thread "main" Exception in thread "Thread-0" java.lang.RuntimeException: 逻辑不完整,数字计数器未执行完毕
at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:12)
at com.flydean.Main2.main(Main2.java:21)
java.lang.RuntimeException: java.lang.thread.interrupt: sleep interrupted
at com.flydean.Main2.lambda$main$0(Main2.java:13)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.flydean.NumberCounter.increaseNumber(NumberCounter.java:17)
at com.flydean.Main2.lambda$main$0(Main2.java:10)
... 1 more
捕获InterruptedException
可以看到,我们捕获到了这个InterruptedException,并且得知具体的原因是sleep interrupted。
从上面的分析可以得知,thread.stop跟thread.interrupt的表现机制是不一样的。thread.stop属于悄悄终止,我们程序不知道,所以会导致数据不一致,从而产生一些未知的异常。
而thread.interrupt会显示的抛出InterruptedException,当我们捕捉到这个异常的时候,我们就知道线程里面的逻辑在执行的过程中受到了外部作用的干扰,那么我们就可以执行一些数据恢复或者数据校验的动作。
在上面的代码中,我们是捕获到了这个异常,打印出异常日志,然后向上抛出一个RuntimeException。
正常情况下我们是需要在捕获异常之后,进行一些处理。
那么自己处理完这个异常之后,是不是就完美了呢?
答案是否定的。
因为如果我们自己处理了这个InterruptedException, 那么程序中其他部分如果有依赖这个InterruptedException的话,就可能会出现数据不一致的情况。
所以我们在自己处理完InterruptedException之后,还需要再次抛出这个异常。
怎么抛出InterruptedException异常呢?
有两种方式,第一种就是在调用Thread.interrupted()清除了中断标志之后立即抛出:
if (Thread.interrupted()) // Clears interrupted status! throw new InterruptedException();
还有一种方式就是,在捕获异常之后,调用Thread.currentThread().interrupt()再次中断线程。
public void run () { try { while (true) { // do stuff } }catch (InterruptedException e) { LOGGER.log(Level.WARN, "Interrupted!", e); // Restore interrupted state... Thread.currentThread().interrupt(); } }
这两种方式都能达到预想的效果。
위 내용은 Java에서 스레드를 종료하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!