多线程核心知识总结(三)——线程停止,中断总结

多线程核心知识总结

三.线程停止,中断

1.讲解原理

原理介绍:使用interrupt了来通知,而不是强制
使用一个线程来通知另一个线程该停止的机制,只是一种通知,如果该线程本身不决定停止,则其不会停止,被停止线程的本身,更熟悉停止自己需要做那些处理和清理工作,所以正确停止线程,是如何使用interrupt合理通知该线程并让该线程配合停止。

2.最佳实践

通常线程会在什么情况下停止

  • run方法的所有代码都运行完毕了
  • 有异常出现,并且方法中没有捕获

正确停止方法:interrupt

  • 通常情况下线程会在什么情况下停止
/** * 描述: run方法内没有sleep或wait方法时,停止线程 */
public class RightWayStopThreadWithoutSleep implements Runnable {
   

    @Override
    public void run() {
   
        int num = 0;
        while (!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2) {
   
            if (num % 10000 == 0) {
   
                System.out.println(num + "是10000的倍数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }

    public static void main(String[] args) throws InterruptedException {
   
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}
  • 线程可能被阻塞
/** * 描述: 带有sleep的中断线程的写法 */
public class RightWayStopThreadWithSleep {
   

    public static void main(String[] args) throws InterruptedException {
   
        Runnable runnable = () -> {
   
            int num = 0;
            try {
   
                while (num <= 300 && !Thread.currentThread().isInterrupted()) {
   
                    if (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.sleep(500);
        thread.interrupt();
    }
}
  • 如果线程在每次迭代后都阻塞
/** * 描述: 如果在执行过程中,每次循环都会调用sleep或wait等方法,那么不需要每次迭代都检查是否已中断 */
public class RightWayStopThreadWithSleepEveryLoop {
   
    public static void main(String[] args) throws InterruptedException {
   
        Runnable runnable = () -> {
   
            int num = 0;
            try {
   
                while (num <= 10000) {
   
                    if (num % 100 == 0) {
   
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

自动清除中断信号

/** * 描述: 如果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();
    }
}

中断线程的两种姿势之优先抛出

/** * 描述: 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();
    }
}

中断线程的两种姿势之恢复中断

/** * 描述:在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();
    }
}

总结

  • 优先选择:传递中断,使用throw抛出异常到run方法内集中解决

  • 不想或者无法传递: 恢复中断,在catch中,重新interrupt

  • 不应该屏蔽中断

相应线程中断的方法总结

  • Object.wait()/wait(long)/wait(long,int)
  • Thread.sleep(long)/sleep(long,int)
  • Thread.join()/join(long)/join(long,int)
  • java.util.concurrent.BlockingQueue.take()/put(E)
  • java.util…concurrent.locks.Lock.lockIntrruptibly()
  • java.util.concurrent.CountDownLatch.await()
  • java.util.concurrent.Exchanger.exchange(V)
  • java.nio.channels.Selector相关方法
  • java.nio.channels.InterruptibleChannel相关方法

正确停止线程

错误停止的方法

  • 被弃用的stop,suspend,resume方法
/** * 描述: 错误的停止方法:用stop()来停止线程,会导致线程运行一半突然停止,没办法完成一个基本单位的操作(一个连队),会造成脏数据(有的连队多领取少领取装备)。 */
public class StopThread implements Runnable {
   

    @Override
    public void run() {
   
        //模拟指挥军队:一共有5个连队,每个连队10人,以连队为单位,发放武器弹药,叫到号的士兵前去领取
        for (int i = 0; i < 5; i++) {
   
            System.out.println("连队" + i + "开始领取武器");
            for (int j = 0; j < 10; j++) {
   
                System.out.println(j);
                try {
   
                    Thread.sleep(50);
                } catch (InterruptedException e) {
   
                    e.printStackTrace();
                }
            }
            System.out.println("连队"+i+"已经领取完毕");
        }
    }

    public static void main(String[] args) {
   
        Thread thread = new Thread(new StopThread());
        thread.start();
        try {
   
            Thread.sleep(1000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
        thread.stop();
    }
}
  • 用volatile设置boolean标记位
/** * 描述: 演示用volatile的局限:part1 看似可行 */
public class WrongWayVolatile implements Runnable {
   

    private volatile boolean canceled = false;

    @Override
    public void run() {
   
        int num = 0;
        try {
   
            while (num <= 100000 && !canceled) {
   
                if (num % 100 == 0) {
   
                    System.out.println(num + "是100的倍数。");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
   
        WrongWayVolatile r = new WrongWayVolatile();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(5000);
        r.canceled = true;
    }
}

/** * 描述:演示用volatile的局限part2 陷入阻塞时,volatile是无法线程的 此例中,生产者的生产速度很快,消费者消费速度慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费 */
public class WrongWayVolatileCantStop {
   

    public static void main(String[] args) throws InterruptedException {
   
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
   
            System.out.println(consumer.storage.take()+"被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了。");

        //一旦消费不需要更多数据了,我们应该让生产者也停下来,但是实际情况
        producer.canceled=true;
        System.out.println(producer.canceled);
    }
}

class Producer implements Runnable {
   

    public volatile boolean canceled = false;

    BlockingQueue storage;

    public Producer(BlockingQueue storage) {
   
        this.storage = storage;
    }


    @Override
    public void run() {
   
        int num = 0;
        try {
   
            while (num <= 100000 && !canceled) {
   
                if (num % 100 == 0) {
   
                    storage.put(num);
                    System.out.println(num + "是100的倍数,被放到仓库中了。");
                }
                num++;
            }
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        } finally {
   
            System.out.println("生产者结束运行");
        }
    }
}

class Consumer {
   

    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
   
        this.storage = storage;
    }

    public boolean needMoreNums() {
   
        if (Math.random() > 0.95) {
   
            return false;
        }
        return true;
    }
}
/** * 描述: 用中断来修复刚才的无尽等待问题 */
public class WrongWayVolatileFixed {
   

    public static void main(String[] args) throws InterruptedException {
   
        WrongWayVolatileFixed body = new WrongWayVolatileFixed();
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

        Producer producer = body.new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = body.new Consumer(storage);
        while (consumer.needMoreNums()) {
   
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了。");


        producerThread.interrupt();
    }


    class Producer implements Runnable {
   

        BlockingQueue storage;

        public Producer(BlockingQueue storage) {
   
            this.storage = storage;
        }


        @Override
        public void run() {
   
            int num = 0;
            try {
   
                while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
   
                    if (num % 100 == 0) {
   
                        storage.put(num);
                        System.out.println(num + "是100的倍数,被放到仓库中了。");
                    }
                    num++;
                }
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            } finally {
   
                System.out.println("生产者结束运行");
            }
        }
    }

    class Consumer {
   

        BlockingQueue storage;

        public Consumer(BlockingQueue storage) {
   
            this.storage = storage;
        }

        public boolean needMoreNums() {
   
            if (Math.random() > 0.95) {
   
                return false;
            }
            return true;
        }
    }
}

停止线程相关重要函数解析

  • 判断是否已经被中断的方法

    • static boolean interrupted()
      返回之后线程中断状态设为false,会清除中断状态
    • boolean isInterrupted()
      返回线程中断状态,但不清除中断状态
/** * 描述: 注意Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自于哪个对象 */
public class RightWayInterrupted {
   

    public static void main(String[] args) throws InterruptedException {
   

        Thread threadOne = new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                for (; ; ) {
   
                }
            }
        });

        // 启动线程
        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());
        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

最终结果为

isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true

常见面试问题

1.如何停止线程:
1.原理:用interrupt来请求,好处(保证数据安全,把中断权限交给被中断线程)
2.想停止线程,要请求方,被停止方,子方法被调用方法相互配合
3.最后再说错误的方法:stop/suspend被废弃,volatile的boolean无法处理长时间阻塞的状态
2.如何处理不可中断的阻塞
1.不存在通用的解决方案,针对特定情况,需要去找可以相应中断的方法

全部评论

相关推荐

菠落蜜:这个是系统自动投的,不是hr主动打招呼。更抽象的还有ai回复
我的秋招日记
点赞 评论 收藏
分享
09-01 09:00
已编辑
四川旅游学院 运营
牛客55195891...:主要是专业不好,别的没毛病
牛客解忧铺
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务