JAVA并发编程-实现线程的方法

195 阅读4分钟

三、线程的停止

1、如何正确停止线程?

使用interrupt来通知,而不是强制。这是一种规范,可以响应interrupt的线程就可以被中断,而有些不响应的线程则不受我们的控制而无法中断,因为被停止的线程比希望其停止的调用方更清楚何时停止/中断。

(1)通常线程会在什么情况下停止普通情况

普通情况就是指runable状态,非阻塞,在程序中也是让它一直处于相加的状态,在几秒内无法运行结束。

public class ThreadsAbout {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            try {
                int num = 0;
                while (num <= 300) {
                    if (!Thread.currentThread().isInterrupted() && num % 100 == 0) {
                        System.out.println(num + "是100的倍数.");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        //thread中300次自加很快,所以主线程在500ms让其中断时,thread应该处于
        //Thread.sleep(1000);的阻塞状态中
        Thread.sleep(500);
        thread.interrupt();
    }
}

子线程catch住了这个中断。

(3)如果线程在每次迭代后都阻塞?

每次循环中都有sleep或wait等情况,那么不需要每次迭代都检查是否已中断。

public class ThreadsAbout {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            try {
                int num = 0;
                while (num <= 10000) {
                    //这里少了中断监视
                    if (num % 10 == 0) {
                        System.out.println(num + "是10的倍数.");
                    }
                    num++;
                    Thread.sleep(10);
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
}

注意到if (num % 10 == 0)没有!Thread.currentThread().isInterrupted()的判断,因为中断都被catch住了.

while内try/catch的问题

/**
 * 描述:     如果while里面放try/catch,会导致中断失效
 */
public class CantInterrupt {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            //为什么这里加了中断检测还是会 !!不中断!!
            while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍数");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

结果是这样的

sleep在设计中,在catch住了中断后会把中断标志位给清除,所以后面是没有检测到线程中断的。

2、开发中的两种实践

  • 优先选择:传递中断

下游服务的方法直接抛出异常,让上游服务去进行处理。要注意的是run()不能继续向上抛异常,因为是重写的run接口,该接口本身不抛异常。

/**
 * 描述:     最佳实践:catch了InterruptedExcetion之后的优先选择:在方法签名中抛出异常 那么在run()就会强制try/catch
 */
public class RightWayStopThreadInProd implements Runnable {

    @Override
    public void run() {
        while (true && !Thread.currentThread().isInterrupted()) {
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                //保存日志、停止程序
                System.out.println("保存日志");
                e.printStackTrace();
            }
        }
    }

    private void throwInMethod() throws InterruptedException {
            Thread.sleep(2000);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadInProd());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
  • 不想或无法传递:恢复中断
/**
 * 描述:最佳实践2:在catch子语句中调用Thread.currentThread().interrupt()来恢复设置中断状态,以便于在后续的执行中,依然能够检查到刚才发生了中断
 * 回到刚才RightWayStopThreadInProd补上中断,让它跳出
 */
public class RightWayStopThreadInProd2 implements Runnable {

    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupted,程序运行结束");
                break;
            }
            reInterrupt();
        }
    }

    private void reInterrupt() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        //这一行很关键,相当于传递中断
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadInProd2());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

  • 不应该屏蔽中断

3、错误的停止方法

  • 被弃用的stop,suspend,resume方法

但是stop方法确实会关掉monitor的锁。什么是monitor

  • volatile设置boolean标记位

4、停止线程相关方法

  • static boolean interrupted() 判断是否中断,该方法会清除中断标志位

  • boolean isinterrupted()判断是否中断,该方法不会清除中断标志位

  • Thread.interrupted()的目的对象是当前线程,而不管使用的方法来自于哪个对象。

// 启动线程 
        threadOne.start();
        //设置中断标志
        threadOne.interrupt();
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        //获取中断标志并重置
        System.out.println("isInterrupted: " + threadOne.interrupted());
        //获取中断标志并重直
        System.out.println("isInterrupted: " + Thread.interrupted());
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());

答案是 true,false(调用静态方法,实际上是判断main的线程是否中断),false(同上),true(第一个isInterupted()是实例方法,不会清除中断标志位)

问题:如何停止线程

  • 原理:用interrupt请求,好处(保证数据安全,把主动权交给被中断方)

  • 想要停止线程,需要请求方(发出中断请求的一方),被停止方(响应中断的一方,需要配合请求方发出的中断,比如检查中断对其响应),子方法(被停止方的线程中可能调用到的方法,如果这里面有中断等情况,最好向上抛出,如果不行则传递)

  • stop/suspend已废弃,volatile的boolean无法处理长时间阻塞的情况

    sychronize(u)
    {
        //这里长时间阻塞,那么u的状态改变没办法改变其阻塞状态
    }