【并发那些事 】创立线程的三种方式

作者 : 开心源码 本文共2734个字,预计阅读时间需要7分钟 发布时间: 2022-05-13 共222人阅读

创立线程可以说是并发知识中最基础的操作了,JDK 提供的创立线程的方式,假如不包括通过线程池的话,目前有三种形式,它们分别是通过继承 Thread 类,通过实现 Runable 接口,通过 FutureTask。如下图所示

下面整理了一下 3 种方法的具体使用与异同。

创立线程的 3 种方法

1. 继承 Thread

  1. 创立一个类继承 Thread 并覆盖 run 方法
class MyThread extends Thread {    @Override    public void run() {        String threadName = getName();        for (int i = 0; i < 20; i++) {            System.out.println("线程[" + threadName + "]运行开始,i = " + i + " time = " + new Date());        }    }}
  1. 创立并启动线程
        MyThread myThread1 = new MyThread();        MyThread myThread2 = new MyThread();        myThread1.start();        myThread2.start();        String threadName = Thread.currentThread().getName();        for (int i = 0; i < 20; i++) {            System.out.println("线程[" + threadName + "]运行开始,i = " + i + " time = " + new Date());        }

整体流程如下:

这里步骤比较简单和清晰

  1. 创立一个类,这类要继承 Thread
  2. 覆盖 Thread 的 run 方法,并在此方法中实现你的多线程任务
  3. 创立这个类的实例
  4. 调用它的 start() 方法(这里要注意,新手容易直接调用 run 方法,那样只是普通调用,而不多线程调用)

2. 实现 Runable

  1. 创立一个类实现 Runable 接口,并覆盖 run 方法
class MyRunable implements Runnable {        @Override    public void run() {        String threadName = Thread.currentThread().getName();        for (int i = 0; i < 20; i++) {            System.out.println("线程[" + threadName + "]运行开始,i = " + i + " time = " + new Date());        }    }}
  1. 创立类的实现,并将它传给 Thread 的构造函数来创立 Thread
MyRunable myRunable = new MyRunable();new Thread(myRunable).start();new Thread(myRunable).start();String threadName = Thread.currentThread().getName();for (int i = 0; i < 20; i++) {    System.out.println("线程[" + threadName + "]运行开始,i = " + i + " time = " + new Date());}

整体流程如下:

具体步骤如下:

  1. 创立一个实现了 Runable 接口的类
  2. 覆盖 run 方法,并在此方法中实现你的多线程任务
  3. 创立 Runable 接口实现类的实例
  4. 通过把上步得到的 Runable 接口实现类的实例,传给 Thread 的有参构造函数来创立 Thread 的实例
  5. 调用上步得来的 Thread 实例的 start() 方法(这里要注意,新手容易直接调用 run 方法,那样只是普通调用,而不多线程调用)

3. 通过 FutureTask

  1. 创立一个实现了 Callable<T> 接口的类,并实现call 方法 (T 代表你想要的返回值类型)
class MyCallerTask implements Callable<String> {        @Override    public String call() throws Exception {        System.out.println("执行任务开始");        Thread.sleep(3000);        System.out.println("执行任务结束");        return "hello";    }}
  1. 创立并启动线程
        // 创立异步任务        FutureTask<String> futureTask = new FutureTask<>(new MyCallerTask());        // 启动线程        new Thread(futureTask).start();        System.out.println("其它操作");        try {            // 等待任务执行完,并取得任务执行完的结果            String result = futureTask.get();            System.out.println(result);        } catch (InterruptedException e) {            e.printStackTrace();        } catch (ExecutionException e) {            e.printStackTrace();        }

整体流程如下:

具体步骤如下:

  1. 创立一个实现了 Callable<T> 接口的类,这里 T 的类型就是你线程任务想要返回的类型
  2. 覆盖 call 方法,并在此方法中实现你的多线程任务
  3. 创立 Callable 接口实现类的实例
  4. 通过把上步得到的 Callable 接口实现类的实例,传给 FutureTask 的有参构造函数来创立 FutureTask 的实例
  5. 通过把上步得到的 FutureTask 实例,传给 Thread 的有参构造函数来创立 Thread 的实例
  6. 调用上步得来的 Thread 实例的 start() 方法(这里要注意,新手容易直接调用 run 方法,那样只是普通调用,而不多线程调用)
  7. 假如你还想取得返回值,需要再调用 **FutureTask **实例的 get() 方法(这里要注意,get()会阻塞线程)

3种方法的优缺点

通过上述的演示代码,可以看出这 3 种方法,其实各有优缺点

复杂程度

通过代码量与逻辑可以显著感觉出来,第一种直接继承 Thread 最方便,并且其它两种到最后,还是要依赖创立 Thread 才能实现。所以从方便及难易程度来看,可以得到如下结论:

可扩展性

通过演示代码可以看出,只有第一种是通过继承,其它两种是通过实现接口的形式。我们都知道 JAVA 是不允许多继承,但是可以多实现。所以假如使用了第一种方法,就无法再继承别的类了。另外第一种把线程与线程任务冗余在了一起,不利于后期的维护。所以可以得到如下结论:

能否有返回值

从代码中可以很容易看出,只有通过 FutureTask 的方式才有返回值,另外两种均没有,所以得出如下结论

该用哪种方式创立线程

假如要用到返回值,那不用想,一定只能使用 FutureTask 的方法。假如对于返回值没有要求,那Thread 与 Runable 均可,不过,考虑到可扩展性,最好使用 Runable 的形式。不过,话说回来,假如在真正项目中使用,综合考虑,一般还是最推荐通过线程池去创立。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 【并发那些事 】创立线程的三种方式

发表回复