前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Java线程基础】01.线程实现方式千万种,为何独终于你一种.md

【Java线程基础】01.线程实现方式千万种,为何独终于你一种.md

作者头像
TodoCoder
发布2022-09-23 09:34:37
1550
发布2022-09-23 09:34:37
举报
文章被收录于专栏:Coder栏

在程序开发中,并发编程是所有程序员必须掌握的技能,而多线程是并发编程中基础中的基础,要想进一步的写出高性能的Java程序,必须要先实现多线程,才可以继续后续的一系列开发,所以我们本节从并发编程的基础如何实现线程开始讲起。

尽管线程的实现很基础,看似很简单,但实际上却暗藏玄机。因为在我们的开发中,不只是仅仅会实现线程就万事大吉,我们还要考虑以下问题:

  1. 实现线程是否对后续的开发有扩展的支持。
  2. 实现线程的开销是否大于收益。
  3. 如何合理的选出一种实现方式。

要想解答以上问题,我们需要先看以下两个问题:

  1. 实现线程的方式到底有几种?
  2. 线程实现的本质是什么?为什么说线程只有一种实现?

实现线程的方式到底有几种?

大部分人会说有 2 种、3 种或是 4 种,少有人说有 1 种。我们先看大家熟知的2种方式

1. 实现 Runnable 接口

代码语言:javascript
复制
public class ThreadImplement {

    static class RunnableThread implements Runnable {
        @Override
        public void run() {
            System.out.println("实现Runnable接口实现线程");
        }
    }
    public static void main(String[] args) {
        RunnableThread runnableThread = new RunnableThread();
        new Thread(runnableThread).start();
    }
}

第 1 种方式是通过实现 Runnable 接口实现多线程,如代码所示,首先通过 RunnableThread 类实现 Runnable 接口,然后重写 run() 方法,之后只需要把这个实现了 run() 方法的实例传到 Thread 类中调用start()方法就可以启动线程了。

2. 继承 Thread 类

代码语言:javascript
复制
public class ThreadImplement {

    static class ThreadExtends extends Thread {
        @Override
        public void run() {
            System.out.println("继承 Thread 类实现线程");
        }
    }
    public static void main(String[] args) {
        new ThreadExtends().start();
    }
}

第 2 种方式是通过继承Thread类,如代码所示,ThreadExtends继承Thread类,并重写run()方法来实现线程功能。

3. 线程池创建线程

有了前两种基本的创建方式,那么为什么说还有第 3 种或第 4 种方式呢?我们先来看看第3种线程池方式的实现。

代码语言:javascript
复制
public class ThreadImplement {

    private static final ExecutorService executorService = new ThreadPoolExecutor(
            10,
            10,
            60L, 
            TimeUnit.SECONDS, 
            new LinkedBlockingQueue(), 
            Executors.defaultThreadFactory());

    public static void main(String[] args) {
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池创建线程");
            }
        });
    }
}

这里创建了一个核心线程数为10,最大线程数也为10的线程池,给线程池传入一个Runnable实例,以此来运行。最后一个参数为 DefaultThreadFactory实例,这是个默认的线程工厂实例。其实线程池实现本质上是通过线程工厂创建的Thread实例来运行 Runnable实例来实现的,只不过这里线程池参数会多点。

在面试中,如果不了解线程池实现原理,答出这个就会给自己挖了“坑”。 所以我们在回答线程实现的问题时,描述完前两种方式,可以进一步引申说“我还知道线程池和Callable 也是可以创建线程的,但是它们本质上也是通过前两种基本方式实现的线程创建。”这样的回答会成为面试中的加分项。然后面试官大概率会追问线程池的构成及原理,这部分内容会在后面的文章中详细分析。

4. Callable 创建线程

代码语言:javascript
复制
public class ThreadImplement {
  
    static class CallableThread implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Callable 创建线程";
        }
    }
    public static void main(String[] args) throws Exception {
        CallableThread callable = new CallableThread();
        FutureTask<String> stringFutureTask = new FutureTask<>(callable);
        new Thread(stringFutureTask).start();
       String r = stringFutureTask.get();
        System.out.println(r);
    }
}

通过有返回值的 Callable 创建线程,Runnable 创建线程是无返回值的,而 Callable 和与之相关的 Future、FutureTask,它们可以把线程执行的结果作为返回值返回,如代码所示,实现了 Callable 接口,然后把 callable包装成一个FutureTask,通过Thread 来启动线程,并获取结果。

无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,是需要被执行的,而不是说它们本身就是线程。它们可以传到Thread中,也可以在线程池中执行。

我们看一下FutureTask的父类

可以看出,FutureTask也是实现的Runnable接口,Thread.start 启动线程的时候,触发的是Runnable的run()方法,进而触发Callable的call()方法。这里涉及到一个设计模式,适配器模式,FutureTask中的 RunnableAdapter类把 Callable和Runnable进行适配来达到 Runnable.run()调用Callable.call()的功能。详细内容我们这里先不讨论。

其实第3、4种方式本质也是实现Runnable接口。

线程实现的本质是什么?为什么说线程只有一种实现?

经过上面的讨论,我们可以得知,第2、3、4种的本质都是通过实现Runnable接口来触发run()方法来实现多线程功能的。那Thread和Runnable两种的本质是什么呢?

我们知道这两种方式都是通过Thread.start()方法来启动线程的。我们看一下Thread源码。

代码语言:javascript
复制
public class Thread implements Runnable {
  private Runnable target;
  private Thread(Runnable target) {
    //这里做了逻辑简化
    this.target = target;
  }
  private Thread() {
    //这里做了逻辑简化
    this.target = null;
  }
  ...
    /**
    * 使线程开始执行;Java虚拟机调用该线程的run方法。 
    */
    public synchronized void start() {
            ...
            try {
                start0();
            } finally {
                ...
            }
        }
     private native void start0();

     @Override
     public void run() {
         if (target != null) {
            target.run();
         }
     }
     ...
 )

说明:start0()方法是 native方法,触发start0()方法后底层会启动线程并在线程中调用 run()方法。

这个是简化过的代码,但主要逻辑都在,先看第1种方式:

代码语言:javascript
复制
RunnableThread runnableThread = new RunnableThread();
new Thread(runnableThread).start();

这个调用start()方法的时候,target != null ,调用的是 RunnableThread中的run()方法。

第2种方式:

代码语言:javascript
复制
public class ThreadImplement {

    static class ThreadExtends extends Thread {
        @Override
        public void run() {
            System.out.println("继承 Thread 类实现线程");
        }
    }
    public static void main(String[] args) {
        new ThreadExtends().start();
    }
}

这个调用start()方法的时候,由于Thread 中的run()方法被 ThreadExtends 重写,其实调用的是 ThreadExtends中的run()方法。

这时我们就可以彻底明白了,事实上创建线程只有一种方式,就是构造一个 Thread 类,这是创建线程的唯一方式。只不过运行的内容来自两个地方。所以实现线程的形式有很多种,本质上只有一种。

那么这两种哪种实现比较好呢?为什么?

实现 Runnable 接口比继承 Thread 类实现线程要好

原因

  1. 从代码的架构考虑,实际上,Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。
  2. 在某些情况下可以提高性能,使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程,执行完任务后线程走到生命周期的尽头被销毁,如果还想执行这个任务,就必须再新建一个继承了 Thread 类的类,如果此时执行的内容比较少。
  3. Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。

综上所述,我们应该优先选择通过实现 Runnable 接口的方式来创建线程。

至于开篇提的几个问题,Runnable方式解耦且易于扩展,如果需要频繁创建线程的话,并且想控制资源的大小可以用线程池的方式,如果想获取线程中的返回值可以用Callable的方式等。

其实讨论完这些后线程的创建方式后怎么使用合理,相信你心中自有答案了。

// TODO Coding ...

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 TodoCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现线程的方式到底有几种?
    • 1. 实现 Runnable 接口
      • 2. 继承 Thread 类
        • 3. 线程池创建线程
          • 4. Callable 创建线程
          • 线程实现的本质是什么?为什么说线程只有一种实现?
          • 那么这两种哪种实现比较好呢?为什么?
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档