如何正确的停止线程?

如何正确停止线程?

原理:使用interrupt来通知,而不是强制关闭;

下面我们来写一下要获取在Integer最大值一半中某个数值的整数倍的数值:

普通情况:

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

结果:

0是10000的倍数
10000是10000的倍数
20000是10000的倍数
30000是10000的倍数
40000是10000的倍数
......
320200000是10000的倍数
320210000是10000的倍数
320220000是10000的倍数
320230000是10000的倍数
任务运行结束了

其中,while中的对线程是否被中断的判断只是是否进行while循环的依据。

遭遇阻塞情况

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

结果

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:16)
	at java.lang.Thread.run(Thread.java:745)

Process finished with exit code 0

线程在运行时执行了sleep()方法,导致线程阻塞,若在线程阻塞时执行interrupt来通知线程中断,则会抛出异常;此次情况就是探究一下在线程阻塞时对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();
    }
}

结果

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at RightWayStopThreadWithSleepEveryLoop.lambda$main$0(RightWayStopThreadWithSleepEveryLoop.java:14)
	at java.lang.Thread.run(Thread.java:745)

Process finished with exit code 0

这种情况与上种阻塞情况不同的是什么呢?可能细心的朋友已经发现while中的条件少了!Thread.currentThread().isInterrupted() 就是对线程是否被中断的判断,其实这是一个没有用的语句,因为在每次迭代中都会进行sleep的阻塞,而且线程大部分时间都用在了sleep中,这样当执行interrupt时,就会在某一个迭代中因为线程的阻塞而产生响应抛出异常。

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 && ) {
                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();
    }
}

结果:

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at CantInterrupt.lambda$main$0(CantInterrupt.java:16)
	at java.lang.Thread.run(Thread.java:745)
500是100的倍数
600是100的倍数
700是100的倍数
800是100的倍数
......

这可能很奇怪,为什么抛出异常后还继续执行呢?可能有的小伙伴说条件判断中缺少!Thread.currentThread().isInterrupted()的判断,满足判断条件还是能继续执行,如果加上!Thread.currentThread().isInterrupted()后判断线程为中断状态就不满足执行循环的条件就会跳出循环,当我们加上后会神奇的发现,结果还是会在抛出异常后继续执行,那是为什么呢?

经过查阅是由于java语言在设计时有这样一个理念:一旦线程响应中断,就会把中断这个标记位给删除,所以在这个代码中还是会满足条件继续执行的。

实践开发中的两种最佳方案:

优先选择:传递中断:

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

如果自己负责的是run的业务逻辑部分,而只是调用别人写的throwInMethod很可能就会使得中断信号被吞掉,在run中catch这个函数的错误然后进行相关的中断就可以了。

不想或无法传递:回复中断

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

利用创建的子方法来使得中断信息能够传递到run中并且能够跳出循环;

牢记:不应屏蔽中断

屏蔽中断:既不在方法签名中去抛出异常,也不在catch语句中恢复中断。

错误的使用方法:

stop、suspend和resume;

stop是因为不安全,停止线程会导致它释放掉已锁定的所有监视器;
suspend和resume是因为它让一个线程挂起,然后携带锁去休息,可能会造成死锁的情况;

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;
    }
}


结果:

0是100的倍数。
100是100的倍数。
200是100的倍数。
300是100的倍数。
400是100的倍数。
500是100的倍数。
600是100的倍数。
700是100的倍数。
800是100的倍数。
900是100的倍数。
1000是100的倍数。
1100是100的倍数。
1200是100的倍数。
1300是100的倍数。
1400是100的倍数。
1500是100的倍数。
1600是100的倍数。
1700是100的倍数。
1800是100的倍数。
1900是100的倍数。
2000是100的倍数。
2100是100的倍数。
2200是100的倍数。
2300是100的倍数。
2400是100的倍数。
2500是100的倍数。
2600是100的倍数。

②无法在想关闭的时间关闭

/**
 * 描述:     演示用volatile的局限part2 陷入阻塞时,volatile是无法停止线程的
 * 此例中,生产者的生产速度很快,消费者消费速度慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费
 */
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
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;
    }
}

结果:

0是100的倍数,被放到仓库中了。
100是100的倍数,被放到仓库中了。
200是100的倍数,被放到仓库中了。
300是100的倍数,被放到仓库中了。
400是100的倍数,被放到仓库中了。
500是100的倍数,被放到仓库中了。
600是100的倍数,被放到仓库中了。
700是100的倍数,被放到仓库中了。
800是100的倍数,被放到仓库中了。
900是100的倍数,被放到仓库中了。
0被消费了
1000是100的倍数,被放到仓库中了。
100被消费了
1100是100的倍数,被放到仓库中了。
消费者不需要更多数据了。
true

当producer中stroge.put满的时候会阻塞,没有人去唤醒它,他就自然走不到while循环。无法做到停止线程。

使用interrupt修改后:

/**
 * 描述:     用中断来修复刚才的无尽等待问题
 */
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;
        }
    }
}

结果:

0是100的倍数,被放到仓库中了。
100是100的倍数,被放到仓库中了。
200是100的倍数,被放到仓库中了。
300是100的倍数,被放到仓库中了。
400是100的倍数,被放到仓库中了。
500是100的倍数,被放到仓库中了。
600是100的倍数,被放到仓库中了。
700是100的倍数,被放到仓库中了。
800是100的倍数,被放到仓库中了。
900是100的倍数,被放到仓库中了。
0被消费了
1000是100的倍数,被放到仓库中了。
100被消费了
1100是100的倍数,被放到仓库中了。
200被消费了
1200是100的倍数,被放到仓库中了。
300被消费了
1300是100的倍数,被放到仓库中了。
400被消费了
1400是100的倍数,被放到仓库中了。
500被消费了
1500是100的倍数,被放到仓库中了。
600被消费了
1600是100的倍数,被放到仓库中了。
700被消费了
1700是100的倍数,被放到仓库中了。
800被消费了
1800是100的倍数,被放到仓库中了。
900被消费了
1900是100的倍数,被放到仓库中了。
1000被消费了
2000是100的倍数,被放到仓库中了。
消费者不需要更多数据了。
生产者结束运行
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
	at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
	at volatiledemo.WrongWayVolatileFixed$Producer.run(WrongWayVolatileFixed.java:46)
	at java.lang.Thread.run(Thread.java:745)

Process finished with exit code 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值