java创建线程的方式到底有几种?(详解)

news2025/1/15 20:41:24

创建线程的方式到底有几种?

  • 一,创建多线程的方式
    • 1,官方解释
    • 2,实现Runnable接口
    • 3,继承Thread类
    • 3,二者区别
      • 3.1,本质区别
      • 3.2,优先考虑使用第一种
  • 二,误以为是创建线程的几种新方式
    • 1,线程池创建线程的本质
    • 2,FutureTask和Callable的本质
      • 2.1,FutureTask和Callable和Thread的结合使用
      • 2.2,FutureTask和Runnable和Thread的结合使用
    • 3,定时器工具类创建线程的本质
  • 三,总结

一,创建多线程的方式

1,官方解释

在oracle的官方文档中,其官方文档链接如下:https://docs.oracle.com/javase/8/docs/api/index.html

在jdk8中,很明确的表明了创建线程的方式只有两种,重点就是这句 There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread . The other way to create a thread is to declare a class that implements the Runnable interface.

翻译过来的意思就是说:有两种方法可以创建一个新的执行线程。一种方法是将一个类声明为Thread的子类。创建线程的另一种方法是声明一个实现Runnable 接口的类

这是权威的官方文档说的,创建线程的方式只有两种,接下来分析一下这两种创建线程的方式的优劣和本质,以及分析一下其他的创建线程方式的底层,如线程池,Future等,看看这些创建线程方式的本质是不是就是官方文档上面的两种方式。
在这里插入图片描述

2,实现Runnable接口

其底层就是将实现了Runnable的类作为参数放在创建线程的构造方法中,并且在实现Runnable的类中重写run方法,实现Thread和run方法的解耦

public class RunnableTest implements Runnable{
    public static void main(String[] args) {
        Thread thread = new Thread(new RunnableTest());
        //启动线程
        thread.start();
    }
    //重写run方法
    @Override
    public void run() {
        System.out.println("hello,runnable");
    }
}

3,继承Thread类

其底层就是利用继承的方式创建一个线程,然后在继承Thread的类中重写run方法。

public class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println("hello,Thread");
    }

    public static void main(String[] args) {
        //启动线程
        new ThreadTest().start();
    }
}

3,二者区别

3.1,本质区别

无论是使用方式一还是方式二,最终都是通过new Thread的方式来创建线程,但是二者的本质区别就是run方法在何处使用。实现Runnable方式的run方法在实现类中重写run方法,实现了解耦;而继承Thread的方式,如果在出现多继承的情况下,那么中间的类里面的run方法就可能会被覆盖,从而导致run方法中的内容丢失,运行不了。

准确的讲,就是只有一种方式创建线程,就是通过构造Thewad类来实现,但是从线程的执行单元来看,执行线程的单元有两种,就是上面所说的根据不同位置重写的run方法来区分。

3.2,优先考虑使用第一种

从解耦的角度来看: 方式一中创建Thread线程和run方法耦合开,方式二耦合在一起,因此方式一优先考虑和选择

从资源的节约上来看: 在每次出现一个任务时,方式二都得手动去创建一个线程,那么线程的创建和销毁都会消耗比较大的资源。而方式一只需要实现runnable接口即可,然后将实现的类作为参数加入到Thread()中,而线程Thread可以通过线程池这样的工具创建和管理,这样就可以减少线程的创建和销毁,这样也是优先考虑和选择方式一。

从继承角度来看: 一个子类只能继承一个父类,那么第二种方式是采用继承的方式,那么只能继承这一种类,这样第二种方式大大的限制了可扩展性,并且多继承的话,可能出现父类的run方法被子类重写,导致父类里面的run方法被覆盖。因此也是优先考虑和选择方式一。

所以终上所述,优先选择方式一。即一般创建线程时,优先使用实现Runnable接口这种方式来创建多线程。

二,误以为是创建线程的几种新方式

1,线程池创建线程的本质

在官方文档中,并没有说线程池是一个可以单独的创建线程的一个方式,但是在日常开发中,我们又是经常通过线程池来创建,管理和监控线程池,那么线程池创建线程的本质到底是什么呢?接下来主要分析一下线程池创建线程的底层源码。

ExecutorService executorService = Executors.newCachedThreadPool();

其内部主要是通过一个ThreadPoolExecutor的执行器,而创建线程主要是通过线程工厂创建,因此主要分析这个defaultThreadFactory

public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
	      				  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue < Runnable > workQueue) {
	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
	Executors.defaultThreadFactory(), defaultHandler);
}

然而在这个线程池创建的线程,如下图,在这个newThead方法中,很清楚的可以知道是需要传一个Runnable的实现到这个参数中,然后通过new Thread(target)的方式来创建线程。

public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
    		return t;
}

在这里插入图片描述

即通过线程池的方式创建线程也是使用的是方式一,实现Runaable的接口来完成的,因此使用这个线程池创建线程不能单独作为一种新的创建线程的方式。

2,FutureTask和Callable的本质

2.1,FutureTask和Callable和Thread的结合使用

在这个模式中,先重写call方法,由于在new Thread的构造方法中并没有 Callable 这种类的参数,因此需要借助 FutureTask 这个类来将Callable和这个Thread类就行一个连接。其用法如下

public class FutureTaskTest implements Callable {
    @Override
    public Object call() throws Exception {
        return 4;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建Callable
        FutureTaskTest th = new FutureTaskTest();
        //创建FutureTask,需要绑定Callable
        FutureTask<Integer> futureTask = new FutureTask<Integer>(th);
        //开启线程
        new Thread(futureTask).start();
        //获取Callable的返回值
        Integer result = futureTask.get();
        System.out.println(result);
    }
}

所以说这个FutureTask就是类似于一个中间类,接下来查看一下这个FutureTask这个类的底层,这个类实现了 RunnableFuture 接口,而这个接口继承了这个Runnable这个接口。那么可以说这个FutureTask就是这个Runnable的一个具体的实现了

//实现RunnableFuture接口
class FutureTask<V> implements RunnableFuture<V>{}
//RunnableFuture接口继承了Runnable类
public interface RunnableFuture<V> extends Runnable, Future<V>

如图,得知这个RunnableFuture接口继承了Runnable类,并且这是熟知的大名鼎鼎的 Doug Lea,李二狗大师写的。

在这里插入图片描述

而这种方式开启线程也是使用 new Thread(target) 的方式实现,而这个target又是Runnable的实现,那么这种方式又是符合方式一,实现Runaable的接口来完成的,因此使用这个Callable创建线程不能单独作为一种新的创建线程的方式。

2.2,FutureTask和Runnable和Thread的结合使用

在这个模式中,显而易见使用的是第一种方式,而FutureTask又是Runnable的一个具体的实现,那么这种方式也符合方式一,即通过实现Runnable接口的方式来完成的,因此这种方式也不能作为一种新的创建线程的方式。

public static class RunnableTest implements Runnable {
    @Override
    public void run() {
        System.out.println("RunnableTest");
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    //创建Runnable对象
    RunnableTest runnable = new RunnableTest();
    Integer result = null;
    //创建FutureTask,需要绑定Runnable
    FutureTask<Integer> futureTask = new FutureTask<Integer>(runnable,result);
    new Thread(futureTask).start();
}

3,定时器工具类创建线程的本质

在这个 TimerTasks 类中,通过Timer创建一个单线程的任务定时器,然后通过调用 schedule 方法执行这个任务。

public class TimerTasks {
	public static void main(String[] args) {
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			public void run() {
				System.out.println("=============定时任务已经开启========" + System.currentTimeMillis());
			}
		}, 0, 1000);
	}
}

接下来主要看这个 schedule 方法,如下图。

在这里插入图片描述

重点查看里面的 sched 方法里面的第一个参数,是一个 TimerTask 的抽象类,并且进入这个抽象类的源码又可以发现,这个类实现了 Runnable 接口,那么这种方式也符合方式一,即通过实现Runnable接口的方式来完成的,因此这种方式也不能作为一种新的创建线程的方式。

public abstract class TimerTask implements Runnable {...}

在这里插入图片描述

三,总结

通过上面几种创建线程的方式举例,那么这个创建线程到底有几种方式的答案相信已经呼之欲出了,没错就是一开始所说的两种:一种是通过实现Runnable接口,另一种就是继承Thread类。并且从本质上来说,只有一种,就是通过创建 Thread 类实现,而上面的两种也是通过run方法在不同的位置的实现来区分的。而下面的几种创建线程的方式虽然用的多,但是究其本质,其底层源码还是脱离不了这两种创建线程的方式的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/24242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

react(子传父、父传子)

目录 1. 父传子 数组/对象 的两种写法 2. 子传父&#xff1a; 3. 生成唯一id的库&#xff1a; 4. 对接收的组件进行验证 1. 父传子 数组/对象 的两种写法 function App() {const obj [{age:19},{age:19}]return (<div className"App"><header classNa…

Jmeter常用参数化技巧总结

说起接口测试&#xff0c;相信大家在工作中用的最多的还是Jmeter。 JMeter是一个100&#xff05;的纯Java桌面应用&#xff0c;由Apache组织的开放源代码项目&#xff0c;它是功能和性能测试的工具。具有高可扩展性、支持Web(HTTP/HTTPS)、SOAP、FTP、JAVA 等多种协议。 在做…

uniapp之使用map组件显示接收过来的经纬度

目录 前言 效果图 提示 总代码 分析 1.显示自己位置的属性 2.markers 点标记 前言 由于项目的需求&#xff0c;我需要从主页面接收经纬度&#xff0c;并渲染至地图上面&#xff0c;同时呢&#xff0c;也要在该位置上显示图标标记点&#xff08;红色&#xff09;&#x…

兴业数金 测试 面试真题|面经

兴业数金测试服务中心技术面&#xff08; 一面二面&#xff09; 时间线流程 8.12一面&#xff08;30min&#xff09;->8.31邮件通知二面&#xff0c;填写职位申请表->9.2二面&#xff08;25min&#xff0c;二面需要用小鱼易连&#xff0c;需提前下载和注册&#xff09; …

儿童台灯怎么选对眼睛最好?2022年的现在如何挑选儿童台灯呢

儿童台灯选择首要考虑因素就是护眼&#xff0c;而是否护眼&#xff0c;可以从以下几个角度去看。 一、照度。根据国家标准划分&#xff0c;照度可分为国A和国AA两级&#xff0c;它们可以衡量光线明亮程度和均匀程度&#xff0c;儿童台灯选择国AA级对眼睛最好。 二、显色指数。…

Java#19(面向对象三大特征之一:多态)

目录 一.多态 二.多态中调用成员的特点 三.instanceof关键字 一.多态 多态:同类型的对象,表现出的不同形态 格式:父类类型 对象名称 子类对象; 前提: (1)有继承关系 (2)有父类引用指向子类对象 (3)有方法重构 优点: (1)使用父类作为参数,可以接收所有子类对象 (2)体现多态的…

科技金融企业助力乡村振兴,能有多大新意?

最近几年&#xff0c;越来越多科技互联网企业开始承担起他们的社会责任&#xff0c;成为乡村振兴领域一股不可忽视的力量。作为电商平台&#xff0c;阿里、拼多多、京东助力农产品上行&#xff0c;解决农产品的销售难题&#xff0c;直接为乡村振兴领域做出大贡献&#xff0c;但…

罗丹明PEG活性酯 RB-PEG-NHS,罗丹明聚乙二醇活性酯,Rhodamine-PEG-NHS

产品名称 罗丹明聚乙二醇活性酯 RB-PEG-NHS 中文名称 罗丹明PEG活性酯 活性酯PEG罗丹明 活性酯聚乙二醇罗丹明 英文名称 RB-PEG-NHS RB-PEG-SC Rhodamine-PEG-NHS 分子量 400 600 1000 2000 3400 5000 10000 结构式&#xff1a; CAS N/A 溶解度 溶于DMSO,DMF,DCM&#xff…

Linux进阶-编译工具链

gcc编译器&#xff08;预处理、编译&#xff09; binutils工具集&#xff08;汇编、链接&#xff09; 本地编译&#xff1a;编译工具链和目标程序运行在相同的架构平台。 交叉编译&#xff1a;编译工具链和目标程序运行在不同的架构平台。 ARM-GCC是GCC编译工具链的一个分支…

Spring Data JPA审计

Spring Data JPA为跟踪持久性层的变化提供了很好的支持。通过使用审核&#xff0c;我们可以存储或记录有关实体更改的信息&#xff0c;例如谁创建或更改了实体以及何时进行更改。 我们可以利用实体字段上的CreatedBy,CreatedDate,LastModifiedDate,LastModifiedBy注释来指示 S…

PointNet 和 PointNet++ 作者讲座学习笔记

文章目录前人的工作三维数据的表达形式把点云转化为体素&#xff0c;再用3D CNNPointNet两个挑战置换不变性旋转不变性PointNet的分类网络PointNet的分割网络PointNet的限制PointNet多级点云特征学习分类分割小区域大小参考资料前人的工作 三维数据的表达形式 点云&#xff1…

Adaptive AUTOSAR Technology Sharing

文章目录一、目录二、未来汽车基础设施需求三、整车架构四、CP vs AP五、AP架构1.Execution Management与State Management的关系2.Service-oriented communication2.Diagnostic Management3.Persistency4.Log and Trace5.安全支持6.安全方法7.信息安全8. AutoSar&#xff1a;T…

Selenium4之CDP

相较于以前的版本&#xff0c;Selenium4除了推出了relative Locators&#xff0c;还有一个比较重要的更新就是对于Chrome Dev Tools Protocol的支持。 Chrome Dev Tools Protocol帮助用户监测、检查、调试和模版化Chrome浏览器以及基于Chromium的其它浏览器&#xff08;比如EDG…

Spring Boot 2.x系列【27】应用篇之代码混淆

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Spring Boot版本2.7.0 文章目录概述代码混淆ProGuard使用Maven 插件直接使用工具混淆概述 代码混淆 代码混淆(Obfuscated code)亦称花指令&#xff0c;是将计算机程序的代码&#xff0c;转换成…

创建.NET MAUI程序

.NET MAUI&#xff0c;先说说读音&#xff0c;Maui&#xff0c;英 [ˈmaui]&#xff0c; 美 [ˈmaʊi]&#xff0c;直接读&#xff1a;毛伊&#xff0c;或者读大写字母MAUI。 .NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架&#xff0c;用于使用 C# 和 XAML 创建本机移动和…

混合云和多云:差异和相似之处

一般来说&#xff0c;云计算是服务器的集合&#xff0c;您可以通过 Internet 访问其资源。要访问云服务/资源&#xff0c;您需要一个云服务提供商根据您的业务需求为您提供服务。混合云和多云是两种比较流行的云计算类型&#xff0c;下文主要对两者的差异和相似之处作出详解&am…

CD147单克隆抗体通过酰胺反应偶联到Dox-CMCh-BAPE聚合物胶束/CBZ-AAN-Dox的制备

小编在这里给大家分享了CD147单克隆抗体通过酰胺反应偶联到Dox-CMCh-BAPE聚合物胶束/CBZ-AAN-Dox的制备&#xff0c;和小编一起来看&#xff01; 瑞禧分享-CBZ-AAN-Dox的研究&#xff1a; 通过计算机辅助药物设计和化学合成&#xff0c;我们获得并研究了前药N-苄氧基羰基Ala-…

力扣 146. LRU 缓存

题目 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否则返…

基于PHP+MySQL二手书交易系统

随着时代的变迁和人们的对知识的汲取,人们需要不断的购买一些新的图书来进行学习,但是这些图书在使用过一点时间之后其价值也会在拥有者手中变的没有那么高了,但是对于没有阅读和使用过这本书的人来说其还是具有更好的价值的,如果直接购买新书价格比较昂贵,对于拥有者来说如果将…

6个好用到爆的音频、配乐素材网站,BGM都在这里了

现在只要有一部手机&#xff0c;人人都能成为视频创作者。一个好的视频不能缺少的就是内容、配乐&#xff0c;越来越注重版权的当下&#xff0c;音效素材使用不当造成侵权的案例层出不穷。为了避免侵权&#xff0c;找素材让很多创作者很头疼。 今天我就整理了6个可以免费下载&a…