并发编程学习之线程中断

版权声明: https://blog.csdn.net/Dongguabai/article/details/82318002

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

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()      //判断是否被中断,并清除当前中断状态

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不是运行时异常,也就是说程序必须捕获并且处理它。

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

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

猜你喜欢

转载自blog.csdn.net/Dongguabai/article/details/82318002