Skip to content

线程的正确停止方式 ​

内容来源:本篇为学习整理,转载自网络技术博客(版权归原作者所有):

看在前面 ​

停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作。停止一个线程可以用Thread.stop()方法,但不建议用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是已被废弃的方法。

在java中有以下3种方法可以终止正在运行的线程:

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

  2. 使用stop方法强行终止,但是不推荐这个方法。因为stop和suspend及resume一样都是过期作废的方法。

  3. 使用interrupt方法中断线程。

一、停止不了的线程 ​

interrupt()方法的使用效果并不像for+break语句那样,立即停止循环。调用interrupt方法是在当前线程中打了一个停止标志,并不是真的停止线程。

java
public class InterruptTest {

    public static class MyThread extends Thread {

        @Override
        public void run() {
            for (int i = 0; i < 500000; i++) {
                System.out.println("i=" + (i + 1));
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();

        try {
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

输出结果:

java
...
i=499994
i=499995
i=499996
i=499997
i=499998
i=499999
i=500000

其中interrupt()方法源码如下:

java
/**
 * Interrupts this thread.
 *
 * <p> Unless the current thread is interrupting itself, which is
 * always permitted, the {@link #checkAccess() checkAccess} method
 * of this thread is invoked, which may cause a {@link
 * SecurityException} to be thrown.
 *
 * <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}.
 *
 * <p> If this thread is blocked in an I/O operation upon an {@link
 * java.nio.channels.InterruptibleChannel InterruptibleChannel}
 * then the channel will be closed, the thread's interrupt
 * status will be set, and the thread will receive a {@link
 * java.nio.channels.ClosedByInterruptException}.
 *
 * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
 * then the thread's interrupt status will be set and it will return
 * immediately from the selection operation, possibly with a non-zero
 * value, just as if the selector's {@link
 * java.nio.channels.Selector#wakeup wakeup} method were invoked.
 *
 * <p> If none of the previous conditions hold then this thread's interrupt
 * status will be set. </p>
 *
 * <p> Interrupting a thread that is not alive need not have any effect.
 *
 * @throws  SecurityException
 *          if the current thread cannot modify this thread
 *
 * @revised 6.0
 * @spec JSR-51
 */
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();
}

二、判断线程是否停止状态 ​

Thread.java类中提供了两种方法:

  1. this.interrupted(): 测试当前线程是否已经中断;

  2. this.isInterrupted(): 测试线程是否已经中断;

interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态。interrupted()源码如下:

java
/**
 * Tests whether the current thread has been interrupted.  The
 * <i>interrupted status</i> of the thread is cleared by this method.  In
 * other words, if this method were to be called twice in succession, the
 * second call would return false (unless the current thread were
 * interrupted again, after the first call had cleared its interrupted
 * status and before the second call had examined it).
 *
 * <p>A thread interruption ignored because a thread was not alive
 * at the time of the interrupt will be reflected by this method
 * returning false.
 *
 * @return  <code>true</code> if the current thread has been interrupted;
 *          <code>false</code> otherwise.
 * @see #isInterrupted()
 * @revised 6.0
 */
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态。isInterrupted()源码如下:

java
/**
 * Tests whether this thread has been interrupted.  The <i>interrupted
 * status</i> of the thread is unaffected by this method.
 *
 * <p>A thread interruption ignored because a thread was not alive
 * at the time of the interrupt will be reflected by this method
 * returning false.
 *
 * @return  <code>true</code> if this thread has been interrupted;
 *          <code>false</code> otherwise.
 * @see     #interrupted()
 * @revised 6.0
 */
public boolean isInterrupted() {
    return isInterrupted(false);
}

这两个方法的区别如下。

我们先来看看this.interrupted()方法的解释:测试当前线程是否已经中断,当前线程是指运行this.interrupted()方法的线程。

java
public class InterruptTest {

    public static class MyThread extends Thread {

        @Override
        public void run() {
            for (int i = 0; i < 500000; i++) {
                i++;
                // System.out.println("i=" + (i + 1));
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();

        try {
            Thread.sleep(2000);
            thread.interrupt();

            System.out.println("stop 1??" + thread.interrupted());
            System.out.println("stop 2??" + thread.interrupted());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

运行结果:

java
stop 1??false
stop 2??false

类Run.java中虽然是在thread对象上调用以下代码:thread.interrupt(), 后面又使用

java
System.out.println("stop 1??" + thread.interrupted());
System.out.println("stop 2??" + thread.interrupted());

来判断thread对象所代表的线程是否停止。但从控制台打印的结果来看,线程并未停止。这也证明了interrupted()方法的解释:测试当前线程是否已经中断。这个当前线程是main,它从未中断过,所以打印的结果是两个false.

让main线程产生中断效果的写法如下:

java
public static void main(String[] args){
    Thread.currentThread().interrupt();
    System.out.println("stop 1??" + Thread.interrupted());
    System.out.println("stop 2??" + Thread.interrupted());

    System.out.println("End");
}

运行效果为:

java
stop 1??true
stop 2??false
End

方法interrupted()的确判断出当前线程是否是停止状态。但第2个布尔值是false的原因如下。

官方帮助文档中对interrupted方法的解释:测试当前线程是否已经中断。线程的中断状态由该方法清除。 换句话说,如果连续两次调用该方法,则第二次调用返回false。

下面来看inInterrupted()方法。

java
public static void main(String[] args) {
    Thread thread = new MyThread();
    thread.start();
    thread.interrupt();
    System.out.println("stop 1??" + thread.isInterrupted());
    System.out.println("stop 2??" + thread.isInterrupted());
}

运行结果:

java
stop 1??true
stop 2??true

其中this.isInterrupted(): 测试线程是否已经中断;上面调用了interrupt()方法,所以输出两个true。

三、能停止的线程--异常法 ​

有了前面学习过的知识点,就可以在线程中用for语句来判断线程是否是停止状态。如果是停止状态,则后面的代码不再运行即可:

java
public class InterruptTest {

    public static class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 500000; i++) {
                if (this.isInterrupted()) {
                    System.out.println("线程已经终止,for循环不再执行");
                    break;
                }
                System.out.println("i = " + (i + 1));
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();

        try {
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

运行结果:

java
...
i=202053
i=202054
i=202055
i=202056
线程已经终止, for循环不再执行

上面的示例虽然停止了线程,但如果for语句下面还有语句,还是会继续运行的。看下面的例子:

java
public static class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 500000; i++) {
            if (this.isInterrupted()) {
                System.out.println("线程已经终止,for循环不再执行");
                break;
            }
            System.out.println("i = " + (i + 1));
        }

        System.out.println("这是for循环外面的语句,也会被执行");
    }
}

使用Run.java执行的结果是:

java
···
i = 411434
i = 411435
i = 411436
i = 411437
i = 411438
i = 411439
i = 411440
线程已经终止,for循环不再执行
这是for循环外面的语句,也会被执行

解决语句继续运行问题的更新代码如下:

java
public static class MyThread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 500000; i++) {
                if (this.isInterrupted()) {
                    System.out.println("线程已经终止,for循环不再执行");
                    throw new InterruptedException();
                }
                System.out.println("i = " + (i + 1));
            }

            System.out.println("这是for循环外面的语句,也会被执行");
        } catch (InterruptedException e) {
            System.out.println("进入MyThrad.java类中的catch了......");
            e.printStackTrace();
        }
    }
}

使用Run.java运行的结果如下:

java
···
i = 402032
i = 402033
i = 402034
i = 402035
线程已经终止,for循环不再执行
进入MyThrad.java类中的catch了......
java.lang.InterruptedException
	at com.demotransfer.java.basic.InterruptTest$MyThread.run(InterruptTest.java:12)

四、在沉睡中停止 ​

线程在sleep()状态下停止线程的效果如下。

java
public class MyThread extends Thread {
    public void run(){
        super.run();

        try {
            System.out.println("线程开始。。。");
            Thread.sleep(200000);
            System.out.println("线程结束。");
        } catch (InterruptedException e) {
            System.out.println("在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:" + this.isInterrupted());
            e.printStackTrace();
        }

    }
}

使用Run.java运行的结果是:

java
线程开始。。。
在沉睡中被停止, 进入catch, 调用isInterrupted()方法的结果是:false
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at thread.MyThread.run(MyThread.java:12)

从打印的结果来看, 如果在sleep状态下停止某一线程,会进入catch语句。并且清除停止状态值,使之变为false。

前一个实验是先sleep然后再用interrupt()停止,与之相反的操作在学习过程中也要注意:

java
public class MyThread extends Thread {
    public void run(){
        super.run();
        try {
            System.out.println("线程开始。。。");
            for(int i=0; i<10000; i++){
                System.out.println("i=" + i);
            }
            Thread.sleep(200000);
            System.out.println("线程结束。");
        } catch (InterruptedException e) {
             System.out.println("先停止,再遇到sleep,进入catch异常");
            e.printStackTrace();
        }

    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        thread.interrupt();
    }
}

运行结果:

java
i=9998
i=9999
先停止,再遇到sleep,进入catch异常
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at thread.MyThread.run(MyThread.java:15)