并发编程学习之线程中断

一般来说,线程在执行完毕后就会结束,无须手动关闭。但是,凡是也有例外,一些服务端的后台程序可能会常驻系统,它们通常不会正常终结。比如,他们的执行体本身就是一个大的无穷循环。

先来个总结:一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。

stop()

在Thread中有一个stop()方法,如果使用stop()方法可以立即将一个线程中止,但是这个方法已经被废弃。因为这个方法是在是太过暴力,会强行将执行一般的线程直接终止,并且会立即释放这个线程所持有的锁,这样就会引发很多问题。

下面简单实现一个安全的停止线程的方式:

//......
public class StopThreadTest extends Thread {

    volatile boolean stopMe = false;

    public void stopMe(){
        stopMe = true;
    }

    @Override
    public void run() {
        while (true){
            if (stopMe){
                System.out.println("线程【"+this.getName()+"】stop......");
                break;
            }
            //do sth
            System.out.println("正在执行!!!");
            Thread.yield();
        }
    }
}
//......

suspend()和resume()

suspend()(挂起)和resume()(继续执行)是一对相反的操作,被挂起的线程必须要等到resume()后才能够继续执行。看着好像很好用,但是这两个方法也已经被废弃了。不推荐使用suspend()的原因是因为suspend()在导致线程暂停的同时,并不会释放任何资源,这样就会有个问题,如果当前线程A持有一把锁,当A被挂起之后,其他任何线程想要访问被线程A占用的锁时,都会被牵连,导致其他线程无法正常地运行,除非线程A执行了resume()方法,而一般线程A的resume()方法肯定是由另一个线程执行的,而线程的很多操作是难以控制的,如果resume()方法意外地在suspend()方法前面执行了,那么线程A就难以继续执行,而它持有的锁也不会释放这样就会引发死锁问题。

而且被挂起的线程是处于RUNNABLE(具体可以参看:并发编程学习之线程的生命周期)状态,这样会验证影响我们对系统当前状态的判断。

以下代码示例简单演示了使用suspend()会出现的问题:

package dgb.test.concurrent;

/**
 * @author Dongguabai
 * @date 2018/9/2 19:35
 */
public class SuspendTest {

    public static void main(String[] args) throws InterruptedException {
        ChangeObjectThread c1 = new ChangeObjectThread("thread-1");
        ChangeObjectThread c2 = new ChangeObjectThread("thread-2");

        c1.start();
        c2.start();
       // Thread.sleep(1000);
        c1.resume();
        c2.resume();
    }

    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            System.out.println("线程【" + getName() + "】正在执行");
            Thread.currentThread().suspend();
        }
    }
}

测试结果:

由于resume()意外的在suspend()前面执行了。两个线程不会退出,而是会挂起。可以查看线程信息看看(具体查看方法可以参看:jstack使用):

这时候发现线程的状态是RUNNABLE的,但是实际上它是被挂起的,这样会使我们误判当前线程的状态。

这里有一个示例使用wait()和notify()实现suspend()和resume(),思路和上面是一样的:

package dgb.test.concurrent;

import java.time.LocalDateTime;

/**
 * @author Dongguabai
 * @date 2018/9/2 19:35
 */
public class SuspendTest2 {

    private static final Object u = new Object();

    public static void main(String[] args) throws InterruptedException {
        ChangeObjectThread c1 = new ChangeObjectThread("thread-1");
        ReadThread r = new ReadThread();

        c1.start();
        r.start();
        Thread.sleep(1000);
        c1.mySuspend();
        Thread.sleep(60000);
        c1.myResume();
    }

    public static class ChangeObjectThread extends Thread {

        private volatile boolean mySuspend = false;

        public ChangeObjectThread(String name) {
            super(name);
        }

        public void mySuspend() {
            // System.out.println("线程【" + getName() + "】mySuspend==");
            mySuspend = true;
        }

        public void myResume() {
            //System.out.println("线程【" + getName() + "】myResume==");
            mySuspend = false;
            synchronized (this) {
                this.notify();
                //System.out.println("线程【" + getName() + "】执行notify==");
            }
        }

        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    while (mySuspend) {
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                synchronized (u) {
                    System.out.println("线程【" + getName() + "】正在执行,当前时间:" + LocalDateTime.now());
                }
                Thread.yield();
            }
        }
    }

    public static class ReadThread extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (u) {
                    System.out.println("ReadThread执行。。。。");
                }
                Thread.yield();
            }
        }
    }
}

线程中断

线程的中断时一种重要的线程协作机制,线程中断并不会使线程立即退出,而是给线程发送一个通知,告诉线程,有人想你退出了,至于目标线程后续会如何处理,则是由目标线程自己决定,是与stop()不同的。

关于线程中断有三个方法:

public void Thread.interrupt()   //中断线程

public boolean Thread.isInterrupted()     //判断是否被中断

public static boolean Thread.interrupted()      //判断是否被中断,并清除当前中断状态

interrupt

public void interrupt()

中断线程。

如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException

如果线程在调用 Object 类的 wait()wait(long) 或 wait(long, int) 方法,或者该类的 join()join(long)join(long, int)sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException

如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException

如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。

如果以前的条件都没有保存,则该线程的中断状态将被设置。

中断一个不处于活动状态的线程不需要任何作用。

 

抛出:

SecurityException - 如果当前线程无法修改该线程


interrupted

public static boolean interrupted()

测试当前线程是否已经中断。线程的 中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。

线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回 false 的方法反映出来。

 

返回:

如果当前线程已经中断,则返回 true;否则返回 false

另请参见:

isInterrupted()


isInterrupted

public boolean isInterrupted()

测试线程是否已经中断。线程的 中断状态 不受该方法的影响。

线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回 false 的方法反映出来。

 

返回:

如果该线程已经中断,则返回 true;否则返回 false

另请参见:

interrupted()

Thread.interrupt()方法是一个实例方法。它通知目标线程中断,也就是设置中断标志位。中断标志位表示当前线程已经被中断了。Thread.isInterrupted() 也是一个实例方法,它判断当前线程是否有被中断(检查标志位)。最后的静态方法Thread.interrupted()也是用来判断当前线程的字段状态,但同时会清除当前线程中断标志位状态。

在下面这个示例中,虽然设置了中断标识位,但是线程中并没有中断处理的逻辑,因此,即使线程被设置了中断标志位,但是这个中断不会起任何作用:

如果想让线程在中断后退出,就要增加相应的中断处理代码,这样就不会武断的将线程中止,这样的操作更加安全和优雅:

package dgb.test.concurrent;

/**
 * @author Dongguabai
 * @date 2018/9/2 23:13
 */
public class InterruptedTest {


    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("线程已被中断-------");
                    break;
                }
                System.out.println("正在执行------------");
            }
        }
        );
        thread.start();
        Thread.sleep(1000);
        System.out.println("中断。。。。。");
        thread.interrupt();
    }
}

在Java的API中,很多声明都会抛出InterruptedException异常,这些方法在抛出InterruptedException之前。JVM会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。

InterruptedException不是运行时异常,也就是说程序必须捕获并且处理它。

Thread.sleep()方法会让当前线程休眠若干时间,它会抛出InterruptedException异常。InterruptedException不是运行时异常,也就是说程序必须捕获并且处理它,当线程在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;

比如修改一下上面那段代码:

如果在第20行处,线程被中断了,那么程序会进入23行,此时中断标识位已被清除,在这里又执行了一次Thread.interrupt()方法中断自己,只有这样,当再次执行的时候会发现线程已经中断了,从而能够保证数据的一致性和完整性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值