线程类的join、interrupt等方法的使用详解

经常有人会问:如何停止一个线程?线程类Threadjoinsleepinterrupt等方法的具体使用场景是什么?本文就线程的joinsleep方法来看一下具体场景下的表现。

方法定义

join

先看下源码

/**
     * Waits for this thread to die.
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final void join() throws InterruptedException {
        join(0);
    }

源码中提示:Waits for this thread to die.顾名思义,等待线程死亡。
而且该方法会抛出中断异常
那么具体会产生什么效果呢?我们来验证一下。
以下代码是在主线程循环体内部调用其他线程的join方法,其他线程单纯的打印执行结果

/**
   * 程序入口,主线程
   */
public static void main(String[] args) {

    Thread thread = new Thread(new PrintTask("Thread"));
    thread.start();

    for (int i = 0; i < 1000; i++) {
      if (i == 500) {
        try {
          thread.join();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
    System.out.println("程序结束");
  }

/**
   * 子线程任务
   */
private static class PrintTask implements Runnable {

    private String name;

    public PrintTask(String name) {
      this.name = name;
    }

    @Override public void run() {
      for (int i = 0; i < 1000; i++) {
        System.out.println(name + " ------> " + i);
      }
      System.out.println("-----thread finished. -----");
    }
  }

执行以上main方法,输出为:

Thread ------> 995
Thread ------> 996
Thread ------> 997
Thread ------> 998
Thread ------> 999
-----thread finished. -----
程序结束

可以看出,主线程中调用其他线程的join后,必须等待其他线程的结束,才可以继续执行。
也就是说:

自身线程内,他线程join。自身需要等待他线程结束后才会获得执行,影响自身

稍微改动一下,将主线程的join操作移动到其他线程执行体内。

/**
   * 程序入口,主线程
   */
  public static void main(String[] args) {

    Thread thread = new Thread(new PrintTask("Thread"));
    thread.start();

    for (int i = 0; i < 1000; i++) {
      // 让主线程不要太快结束
      Math.random();
    }
    System.out.println("程序结束");
  }
  
  private static class PrintTask implements Runnable {

    private String name;

    public PrintTask(String name) {
      this.name = name;
    }

    @Override public void run() {
      for (int i = 0; i < 1000; i++) {
        System.out.println(name + " ------> " + i);
        if (i == 10) {
          try {
            Thread.currentThread().join();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
      System.out.println("-----thread finished. -----");
    }
  }

执行以上main方法,输出为:

Thread ------> 0
Thread ------> 1
Thread ------> 2
Thread ------> 3
Thread ------> 4
Thread ------> 5
Thread ------> 6
Thread ------> 7
Thread ------> 8
Thread ------> 9
Thread ------> 10
程序结束

可以看出主线程的没有受到其他线程的影响,而自定义线程在join后,线程阻塞,没有继续执行,也没有结束。也就是说:

自身线程内自身join,会造成自身等待,对其他线程没影响

sleep

先看下源码

/**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
     *
     * @param  millis
     *         the length of time to sleep in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public static native void sleep(long millis) throws InterruptedException;

是个静态方法,会导致当前线程阻塞一定时间。这个方法在平时可能用得比较多。这里不提供测试了。

interrupt

先看下源码说明

/**
     * Interrupts this thread.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     */
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

这里只截取了注释的一部分,从注释里可以看出,方法作用是中断线程的,但是从方法体中的注释可以看到

interrupt0();           // Just to set the interrupt flag

方法只是设置了中断标志位。在实际测试中,我们也发现,不管是在线程内部还是外部,当线程正在执行时,调用线程的interrupt方法,都不会对线程的执行有任何影响。那么这个方法到底有什么用呢?
其实从该方法的注释中可以看到:

     * If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.

当线程处于阻塞状态时,调用该中断方法,会引发中断异常。下面来测试一下。

/**
   * 程序入口,主线程
   */
  public static void main(String[] args) {

    Thread thread = new Thread(new PrintTask("Thread"));
    thread.start();

    for (int i = 0; i < 1000; i++) {
      System.out.println("Main Thread ------> " + i);
      if (i == 50) {
        try {
          thread.join();
          thread.interrupt();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
    System.out.println("程序结束");
  }
  
  /**
   * 子线程任务
   */
  private static class PrintTask implements Runnable {

    private String name;

    public PrintTask(String name) {
      this.name = name;
    }

    @Override public void run() {
      for (int i = 0; i < 10000; i++) {
        // 防止线程太快结束
        Math.random();
      }
      System.out.println("-----thread finished. -----");
    }
  }

当次的测试结果为:

Main Thread ------> 49
Main Thread ------> 50
-----thread finished. -----
Main Thread ------> 51
Main Thread ------> 52
......
Main Thread ------> 98
Main Thread ------> 99
程序结束

可以看到,主线程和自定义线程都正常结束。原因也很简单,自定义线程在join后,并没有被阻塞,调用它的中断方法后,不会造成任何影响。

下面改动一下,在自定义线程内部join后,阻塞自定义线程,然后调用自定义线程的中断方法

/**
   * 程序入口,主线程
   */
  public static void main(String[] args) {

    Thread thread = new Thread(new PrintTask("Thread"));
    thread.start();

    for (int i = 0; i < 1000; i++) {
      // 让主线程不要太快结束
      Math.random();
      if (i == 999) {
        System.out.println("主线程内中断自定义线程");
        thread.interrupt();
      }
    }
    System.out.println("程序结束");
  }
  
  
 /**
   * 子线程任务
   */
  private static class PrintTask implements Runnable {

    private String name;

    public PrintTask(String name) {
      this.name = name;
    }

    @Override public void run() {
      for (int i = 0; i < 1000; i++) {
        System.out.println(name + " ------> " + i);
        if (i == 10) {
          try {
            Thread.currentThread().join();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }

      System.out.println("-----thread finished. -----");
    }
  }

程序运行结果为:

Thread ------> 9
Thread ------> 10
主线程内中断自定义线程
程序结束
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Thread.join(Thread.java:1252)
	at java.lang.Thread.join(Thread.java:1326)
	at com.github.javarunable.Application$PrintTask.run(Application.java:56)
	at java.lang.Thread.run(Thread.java:748)
Thread ------> 11
Thread ------> 12

可以看到在自定义线程自身join后,处于等待状态,之后主线程调用了自定义线程的中断方法,自定义线程会收到一个中断异常。
实际测试过程中,只要自定义线程自身join,不管是在自定义线程内部还是外部调用自定义线程的中断方法,自定义线程都会收到一个中断异常。也就是说:

当线程处于阻塞或者等待状态时,调用线程的中断方法,会在线程内部产生一个中断异常

反之也是一样

当线程的中断标记为True时,线程尝试调用自身的join方法阻塞线程时,线程也会收到一个中断异常

Thread ------> 997
------ interrupt state: true
Thread ------> 998
------ interrupt state: true
Thread ------> 999
尝试调用线程join方法
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Thread.join(Thread.java:1252)
	at java.lang.Thread.join(Thread.java:1326)
	at com.github.javarunable.Application$PrintTask.run(Application.java:58)
	at java.lang.Thread.run(Thread.java:748)
-----thread finished. -----

再看如下代码

/**
   * 程序入口,主线程
   */
  public static void main(String[] args) {

    Thread thread = new Thread(new PrintTask("Thread"));
    thread.start();

    for (int i = 0; i < 1000; i++) {
      // 让主线程不要太快结束
      Math.random();
      if (i == 10) {
        System.out.println("主线程内中断自定义线程");
        thread.interrupt();
        try {
          thread.join();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
    System.out.println("程序结束");
  }

在线程外部调用线程的join方法,在前面知道,不会造成线程的阻塞,自然也不会产生中断异常。

在实际测试过程中,sleep和阻塞式join产生的效果是一致的。

解决问题

如何停止一个线程?

在Thread类中有一个过时方法stop,我们看一下他的说明

     *       @deprecated This method is inherently unsafe.  Stopping a thread with
     *       Thread.stop causes it to unlock all of the monitors that it
     *       has locked (as a natural consequence of the unchecked
     *       <code>ThreadDeath</code> exception propagating up the stack).  If
     *       any of the objects previously protected by these monitors were in
     *       an inconsistent state, the damaged objects become visible to
     *       other threads, potentially resulting in arbitrary behavior.  Many
     *       uses of <code>stop</code> should be replaced by code that simply
     *       modifies some variable to indicate that the target thread should
     *       stop running.  The target thread should check this variable
     *       regularly, and return from its run method in an orderly fashion
     *       if the variable indicates that it is to stop running.  If the
     *       target thread waits for long periods (on a condition variable,
     *       for example), the <code>interrupt</code> method should be used to
     *       interrupt the wait.
     */

可以看出,系统不建议使用这种不安全的线程停止方法,而是建议通过变量状态的控制来结束线程。当一个线程处于长时间等待状态时,可以通过设置中断位标记,来中断这种等待。

由此,我们可以得出两种简单的结论。

  1. 通过变量状态控制,来自然结束线程
  2. 通过线程的中断方法和join方法(或者sleep方法)来产生中断异常,在异常处理模块(catch语句块)中调用return等结束语句来结束线程的运行。

线程类Thread的join、sleep、interrupt等方法的具体使用场景是什么?

根据上面的测试结果和描述,我们对于这些方法的使用应该有几个明确的要点:

  1. 明确是线程内部调用还是线程外部调用
  2. 明确方法调用后产生的现象
  3. 明确方法不同调用顺序产生的影响

关于yield

关于Threadyield方法,可以参考转载的一篇文章Thread.yield方法到底有什么用?

发布了36 篇原创文章 · 获赞 9 · 访问量 4877

猜你喜欢

转载自blog.csdn.net/lotty_wh/article/details/104523171