美文网首页
创建停止线程

创建停止线程

作者: Travis_Wu | 来源:发表于2020-11-03 10:14 被阅读0次

一、创建线程

  • 实现 Runnable 接口
    1. 首先通过RunnableThread 类实现 Runnable 接口
    2. 然后重写了 run() 方法
    3. 之后只要把这个实现了 run() 方法的实例传到Thread 类中去就可以实现多线程
    public class RunnableThread implements Runnable {
        @Override
        public void run() {
            System.out.println("用实现Runnable接口实现线程");
        }
    }
  • 继承 Thread 类
    没有实现接口,而是继承 Thread 类,并重写了其中的 run() 方法
    public class ExtendsThread extends Thread {
        @Override
        public void run() {
            System.out.println(“用Thread类实现线程");
        }
    }
  • 线程池创建线程
    1. 默认是采用DefaultThreadFactory
    2. 它会给我们线程池创建的线程设置一些默认的值,比如它的名字,它是不是守护线程,以及它的优先级
  • 有返回值的 Callable
    实现了callable接口,并且给它的泛型设置成 integer,然后它会返回一个随机数回来
    class CallableTask implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            return new Random().nextInt();
        }
    }
    //创建线程池
    ExecutorService service = Executors.newFixedThreadPool(10);
    //提交任务,并用 Future提交返回结果
    Future<Integer> future = service.submit(new CallableTask());
  • 定时器Timer
    TimerTask的实现了Runnable接口,Timer内部有个TimerThread继承自Thread因此绕回来还是Thread +
    class TimerThread extends Thread {
        //具体实现
    }
  • 匿名内部类
    它不是传入一个已经实例好的runnable对象,而是直接在这边去用一个匿名内部类的方式把需要传入的runnable给实现出来
    /**
     *描述:匿名内部类创建线程
     */
    new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();

总结:透过现象看本质,创建线程只有一种方式: 构造Thread类

  • 实现Runnable接口的run 方式,并把Runnable实例作为target对象,传给Thread类,最终调用target.run()
  • 继承Thread类,重写Thread的run方法(),Thread.start()会执行run()
  • 实现runnable接口 要比继承thread类要 更好:
    1. 可以把不同的内容进行解耦,权责分明
    2. 某些情况下可以提升性能,减小开销
    3. 继承 Thread 类相当于限制了代码未来的可扩展性

二、停止线程

  • 原理介绍
    通常情况下我们不会手动停止一个线程,而是允许线程运行到整个进程结束,然后让它自然停止,但依然会有许多特殊的情况 需要我们提前停止线程。比如:用户突然关闭程序或程序运行出错重启
    这种情况下,即将停止的线程在很多业务场景下仍然很有价值,但是Java 并没有提供简单易用,能够直接安全停止线程的能力
  • 为什么不强制停止?而是通知、协作
    对 Java 而言,最正确的停止线程的方式是使用 interrupt,但 interrupt 仅仅起到通知被停止线程的作用。
    对于被停止的线程而言,它拥有完全的自主权,既可以选择立即停止,也可以选择一段时间后停止。
    那么为什么 Java 不提供强制停止线程的能力呢?
    Java 希望程序间能够相互通知、相互协作的管理线程
    比如:
    线程正在写入一个文件,这时收到终止信号,它就需要根据自身业务判断,是选择立即停止,还是将整个文件写入成功后停止,而如果选择立即停止就可能造成数据错乱,不管是中断命令发起者,还是接收者都不希望数据出现问题。
  • 如何用 interrupt 停止线程
    1. 首先通过 Thread.currentThread().isInterrupt() 判断线程是否被中断
    2. 然后判断 count 是否小于 1000
    3. 这个线程的工作内容很简单,就是打印 0~999 的数字,每打印一个数字 count 加 1,可以看到,线程会在每次循环开始之前,检查是否被中断了
    4. 接下来在 main 函数中会启动线程
    5. 然后休眠五毫秒后立刻中断线程,该线程会检测到中断信号,于是在还没打印完1000个数的时候就会停下来
    6. 这种情况就属于通过 interrupt 实现了线程的正确停止
    public class StopThread implements Runnable {
        @Override
        public void run() {
            int count = 0;
            while (!Thread.currentThread().isInterrupted() &&
                    count < 1000) {
                System.out.println("count = " + count++);
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
  • sleep 期间能否感受到中断
    • 当在一个被阻塞的线程(调用sleep 或wait等会让线程阻塞的方法 ) 上调用interrupt 方法时,阻塞调用将会被Interrupted Exception 异常中断,将中段标记位设置成 false
    • 如果我们负责编写的方法需要调用 sleep 或者 wait 时,就要求在方法中使用 try/catch或在方法签名中声明 throws InterruptedException
    • 在 catch 语句块中调用 Thread.currentThread().Interrupt() 函数
private void reInterrupt() {
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      e.printStackTrace();
    }
}

三、为什么用 volatile 标记位的停止方法是错误的

public class VolatileStopThread implements Runnable {
    
    private volatile boolean canceled = false;

    @Override
    public void run() {
        int num = 0;
        try {
            while (!canceled && num <= 1000000) {
                if(num % 10 == 0) {
                    System.out.print(num + "是10的倍数");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileStopThread r = new VolatileStopThread();
        Thread thread = new Thread(r)
        thread.start();
        Thread.sleep(3000);
        r.canceled = true;
    }
}
  1. 声明一个叫作 VolatileStopThread 的类, 它实现了 Runnable 接口
  2. 然后在 run() 中进行 while 循环,在循环体中又进行了两层判断
    • 首先判断 cancled 变量的值
      cancled 变量是一个被 volatile 修饰的初始值为 false 的布尔值,当该值变为 true 时,while 跳出循环
    • while 的第二个判断条件是 num 值小于1000000(一百万)
  3. 在while 循环体里,只要是 10 的倍数就打印出来,然后 num++
  4. 启动线程,然后经过 3 秒钟的时间,把用 volatile 修饰的布尔值的标记位设置成 true
  5. 这样,正在运行的线程就会在下一次 while 循环中判断出cancled 的值已经变成 true 了
  6. 这样就不再满足 while 的判断条件,跳出整个 while 循环,线程就停止了
  7. 这种情况是演示 volatile 修饰的标记位可以正常工作的情况,但是如果我们说某个方法是正确的,那么它应该不仅仅是在一种情况下适用,而在其他情况下也应该是适用的

看一下反例

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 % 50 == 0) {
                    storage.put(num);
                    System.out.println(num + "是50的倍数,被放到仓库中了。");
                }
                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.97) {
            return false;
        }
        return true;
    }
}

public class VolatileCantStop {

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

        ArrayBlockingQueue storage = new ArrayBlockingQueue(8);

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

        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);
    }
}
  1. 这是一个生产者,消费者模式
  2. 对于生产者而言,希望通过 volatile 标记位的方式来停止线程
  3. BlockingQueue 是生产者消费者用来通讯的存储器
  4. 当BlockingQueue 达到阀值8的时候,生产者会进行阻塞
  5. 一旦生产者进入storage.put(num);线程就阻塞了,那么此时无论消费者怎么改变canceled的值,都无法使生产者重新进入while进行判断,那么使用 volatile 来进行停止线程就失败了。

相关文章

  • Java 多线程之线程的创建及其使用

    一、创建线程以及启动线程 二、停止线程 三、线程类中函数详解 一、创建线程以及启动线程 创建线程:Java中创建线...

  • 创建停止线程

    一、创建线程 实现 Runnable 接口首先通过RunnableThread 类实现 Runnable 接口然后...

  • Java多线程(二)

    Java多线程(二) 上一篇“Java多线程(一)”主要讨论的是线程的创建,本章主要讨论停止线程。 1.概述 停止...

  • 线程同步(通信)

    线程分类 普通线程:主线程创建的所有子线程都是普通线程守护线程:JVM停止时,抛弃所有守护线程,不执行finall...

  • Thread和Runnable的区别

    线程Thread的5状态:创建——>就绪——>运行——>阻塞——>停止创建:new就绪:创建对象后,执行start...

  • 高并发(3)- 多线程的停止

    前言 上篇文章讲解了多线程的两种创建方法,那么本文就带来多线程的停止方法。 一、线程的停止 1.stop方法 st...

  • iOS多线程必备知识点-持续更新

    NSThread 1、线程创建方法: 2、线程退出方法: 也可通过停止runloop来退出线程,具体操作参考下面的...

  • 常驻线程

    当创建一个线程,并且希望它一直存在时,但往往我们创建的线程都是执行完成之后也就停止了,不能再次利用,那么如何创建一...

  • 多线程_2_停止和暂停

    线程停止 停止线程是在多线程开发时很重要的技术点,掌握线程停止技术可以对线程的停止进行有效的处理。 停止线程的方法...

  • [笔记]Java多线程概略

    线程的定义和状态 创建、就绪、运行、阻塞、停止 线程优先级 线程优先级的系统规则线程是具有优先级的,高优先级的线程...

网友评论

      本文标题:创建停止线程

      本文链接:https://www.haomeiwen.com/subject/gtrbvktx.html