>  기사  >  Java  >  Java에서 스레드를 종료하는 방법

Java에서 스레드를 종료하는 방법

WBOY
WBOY앞으로
2023-04-26 08:58:201156검색

Thread.stop이 비활성화되는 미스터리

스레드를 종료하는 방법을 묻는 질문에 아마도 대부분의 사람들은 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제