当前位置 博文首页 > 渊渟无迹静涛君的博客:Java创建多线程的方式只有两种

    渊渟无迹静涛君的博客:Java创建多线程的方式只有两种

    作者:[db:作者] 时间:2021-08-16 15:52

    申明:转载

    Java并发方面有很多书籍以及博客,针对于线程创建方式有着不同描述,例如实现Runnable接口、集成Thread类、使用线程池工具类以及结合Callable和Future创建线程等。

    创建线程的两种方式
    Oracle官方文档,即java.lang.Thread类注释的表述是有如下两种创建线程的方式。https://docs.oracle.com/javase/8/docs/api/index.html方式一:实现Runnable接口,传入Thread类

    public class RunnableStyle implements Runnable{
    
    ? ? public static void main(String[] args) {
    ? ? ? ? Thread thread = new Thread(new RunnableStyle());
    ? ? ? ? thread.start();
    ? ? }
    
    ? ? @Override
    ? ? public void run() {
    ? ? ? ? System.out.println("Runnable接口方式创建线程");
    ? ? }
    }


    方式二:继承Thread类,重写run方法

    public class ThreadStyle extends Thread {
    ? ? @Override
    ? ? public void run() {
    ? ? ? ? System.out.println("用Thread方式创建线程");
    ? ? }
    
    ? ? public static void main(String[] args) {
    ? ? ? ? new ThreadStyle().start();
    ? ? }
    }


    两种方式的比较
    推荐采用实现Runnable接口,传入Thread类。

    从代码架构角度:具体执行的任务(run方法)应该和创建/运行线程的机制(Thread类)解耦。
    与重写Thread类run方法一样,实现Runnable接口run方法的内容也是具体执行的任务。但是Runnable方式则可以创建单独任务类实现Runnable接口,然后传入任务实例到Thread类中。这样同一个任务类可以传给不同的Thread,同时任务类不需要负责创建线程等工作,因此是解耦的。

    从继承的角度上,Java是单继承,如果继承了Thread类就无法继承其它类,限制可扩展性。

    从资源节约的角度上讲,如果是Thread类方式,每次新建一个任务只能新建一个独立的线程,会有额外线程创建/销毁等损耗。而Runnable更加方便采用线程池工具,减少创建、销毁线程带来的损耗。

    两种方式本质对比
    两种方式在多线程实现的本质上是一致的,都是调用thread对象的start方法创建线程。主要区别在于run方法的内容来源上。

    # java.lang.Thread
    private Runnable target;
    @Override
    public void run() {
    ? ? if (target != null) {
    ? ? ? ? target.run();
    ? ? }
    }



    实现Runnable方式,最终调用了Runnable对象target的run方法;而继承Thread方式是重写了整个run方法。

    示例:代码同时使用两种方式
    匿名内部类实现Runnable接口,并在类内部重写run方法。重写run方法的代码直接覆盖Thread类run方法代码,Runnable中run方法实现代码不会执行。

    Thread thread = new Thread(new Runnable() {
    ? ? @Override
    ? ? public void run() {
    ? ? ? ? System.out.println("使用Runnable方式创建线程");
    ? ? }
    }) {
    ? ? @Override
    ? ? public void run() {
    ? ? ? ? System.out.println("使用Thread方式创建线程");
    ? ? }
    };
    thread.start();


    多线程的其他代码实现形式
    线程池创建线程的方式

    public class ThreadPoolStyle {
    ? ? public static void main(String[] args) {
    ? ? ? ? // 不提倡的创建线程池方式--原因参见阿里规约
    ? ? ? ? ExecutorService pool = Executors.newCachedThreadPool();
    ? ? ? ? for (int i = 0; i < 1000; i++) {
    ? ? ? ? ? ? pool.submit(new Task());
    ? ? ? ? }
    ? ? }
    }
    
    class Task implements Runnable {
    ? ? @Override
    ? ? public void run() {
    ? ? ? ? try {
    ? ? ? ? ? ? Thread.sleep(1000);
    ? ? ? ? } catch (InterruptedException e) {
    ? ? ? ? ? ? e.printStackTrace();
    ? ? ? ? }
    ? ? ? ? System.out.println(Thread.currentThread().getName());
    ? ? }
    }



    我们通过Executors创建线程池,深入源码可以看到,内部创建ThreadPoolExecutor时会使用ThreadFactory。以DefaultThreadFactory为例,其内部创建线程的方法newThread仍然是通过Runnable创建线程。

    ## java.util.concurrent.Executors
    public Thread newThread(Runnable r) {
    ? ? Thread t = new Thread(group, r,
    ? ? ? ? ? ? ? ? ? ? ? ? ? namePrefix + threadNumber.getAndIncrement(),
    ? ? ? ? ? ? ? ? ? ? ? ? ? 0);
    ? ? if (t.isDaemon())
    ? ? ? ? t.setDaemon(false);
    ? ? if (t.getPriority() != Thread.NORM_PRIORITY)
    ? ? ? ? t.setPriority(Thread.NORM_PRIORITY);
    ? ? return t;
    }


    通过Callable和FutureTask创建线程的方式

    public class CallableFutureTaskStyle {
    ? ? public static void main(String[] args) {
    ? ? ? ? // 创建任务,启动线程
    ? ? ? ? FutureTask<String> futureTask = new FutureTask<>(new CallableTask());
    ? ? ? ? new Thread(futureTask).start();
    ? ? }
    }
    
    class CallableTask implements Callable {
    ? ? @Override
    ? ? public String call() throws Exception {
    ? ? ? ? return "使用Callable和FutureTask创建线程";
    ? ? }
    }



    Callable接口是一个独立的接口,用于创建任务,通常与FutureTask配合使用。而FutureTask的父类是Runnable,因此其本质仍然是Runnable方式创建线程。

    定时器创建线程

    public class TimerStyle {
    ? ? public static void main(String[] args) {
    ? ? ? ? Timer timer = new Timer();
    ? ? ? ? timer.scheduleAtFixedRate(new TimerTask() {
    ? ? ? ? ? ? @Override
    ? ? ? ? ? ? public void run() {
    ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName());
    ? ? ? ? ? ? }
    ? ? ? ? },100,100);
    ? ? }
    }


    定时器任务类TimerTask实现Runnable接口。

    public abstract class TimerTask implements Runnable


    匿名内部类和Lambda代码实现形式
    多线程的代码实现方式有很多种,但其本质仍然是出于继承Thread和实现Runnable接口两种方式。线程池、Callable以及定时器,对创建线程做了一定的封装,但本质仍然没有变。至于线程匿名内部类和Lambda实现,只是代码实现形式的不同,不能作为实现线程的方式。

    new Thread(new Runnable() {
    ? ? @Override
    ? ? public void run() {
    ? ? ? ? System.out.println(Thread.currentThread().getName());
    ? ? }
    }).start();
    new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
    1


    较为准确的描述
    (1)首先从不同的角度看,会有不同的答案。例如从代码实现,还是从本质上说。

    (2)通常我们可以分为两种,分别是实现Runnable接口和继承Thread类。Thread类的注释也是这样表述的。

    (3)描述Runnable方式和Thread方式的不同(3个角度)。

    (4)其实Thread类实现了Runnable接口,类中实现了run方法。可以发现两种方式在创建线程的本质上是一样的,都是调用Thread对象的start方法,主要区别在于run方法内容的来源不同:Runnable方式最终是调用Ruannble对象target的run方法,而Thread方式则是使用了重写的run方法。

    (5)还有其他实现线程的方式,例如线程池也能新建线程,但是细看源码,其本质是Runnable方式。

    (6)准确的讲,创建线程只有一种方式,那就是创建Thread类,而实现线程的run方法有两种方式。除此之外,从表面上看线程池、定时器等工具类也可创建线程,但是本质没有变。


    版权声明:本文为CSDN博主「大唐雨夜」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/LIZHONGPING00/article/details/104118829

    cs
    下一篇:没有了