Java创建线程的方式只有一种:Thread+Runnable

article/2025/9/16 23:54:24

Java创建线程的方式其实只有一种

  • 👨‍🎓一、继承Thread
  • 👨‍🎓二、实现Runnable接口
  • 👨‍🎓三、实现Callable接口
  • 👨‍🎓四、通过线程池创建
  • 👨‍🎓五、总结

一般我们会认为创建线程的方式是三到四种,其实 本质上这四种没有任何区别,都是利用Thread+Runnable来进行实现的多线程,其实java自始至终创建多线程的方式都只有一种,下面一起看下他们是怎么对Runnable进行改造成多种多样的。

👨‍🎓一、继承Thread

继承Thread下面是常见的写法

public class Test1 extends Thread{@Overridepublic void run() {while(true){System.out.println("线程1");}}public static void main(String[] args) {new Test1().start();}}

利用这种方式实现的线程,就是继承Thread类然后重写run方法,我们在run方法内部进行线程逻辑的编写。java的start方法会调用start0方法,start0是一个native方法,底层会调用run方法进行执行线程,所以我们是重写run方法。那run方法是怎么来的呢?
在这里插入图片描述
上面是run方法在Thread中的实现,我们可以看到他被注解Override修饰了,说明这个方法是父类的方法,我们点击左侧红色向上的箭头,就会发现跳到了Runnable接口中,如下
在这里插入图片描述
这样就很简单明了了,所以这样就简单明了了,我们在继承Thread重写run方法时其实重写的是Runnable的run方法。这这种已经证明了我们开始说的java是利用Thread+Runnable实现的多线程了

👨‍🎓二、实现Runnable接口

这种方式也是需要依赖Thread才能去创建线程,如下所示

		public class TestThread {public static void main(String[] args) {new Thread(() -> {while(true)System.out.println("Runnable多线程1");}).start();new Thread(() -> {while(true)System.out.println("Runnable多线程2");}).start();}}

那我们来看下这个实现方式到底是如何进行多线程创建的吧,我们还是从run方法开始,因为Thread启动线程时调用的是自己的run方法,那我们就先看下他自己的run方法实现:
在这里插入图片描述
可以看到Thread的run方法调用的是target.run方法,那target是什么呢?ctrl+左会发现,他就是一个Runnable,那这个Runnable怎么来的呢?我们创建过程中只在Thread的构造方法中传入了Runnable,那是不是在这里咱们一起看下:
在这里插入图片描述
这里没有做什么实质的操作,所以继续看,最后调用到了这里:

private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name.toCharArray();Thread parent = currentThread();SecurityManager security = System.getSecurityManager();if (g == null) {/* Determine if it's an applet or not *//* If there is a security manager, ask the security managerwhat to do. */if (security != null) {g = security.getThreadGroup();}/* If the security doesn't have a strong opinion of the matteruse the parent thread group. */if (g == null) {g = parent.getThreadGroup();}}/* checkAccess regardless of whether or not threadgroup isexplicitly passed in. */g.checkAccess();/** Do we have the required permissions?*/if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;this.daemon = parent.isDaemon();this.priority = parent.getPriority();if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();this.target = target; // 这里是关键点setPriority(priority);if (parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */tid = nextThreadID();}

我们会发现这个方法内部将我们从构造器中传入的Runnable对象放到了this.target中,所以可以看到Thread中的run方法调用的就是我们重写的run方法。所以这个也证明了java中是通过Thread+Runnable实现的多线程方式。

👨‍🎓三、实现Callable接口

这种方式我们需要依赖FutrureTask,其实我们可以接受返回值也得归功FutureTask,下面是常见的实现代码:

public class TestThread {public static void main(String[] args) throws Exception{FutureTask<String> futureTask = new FutureTask<>(()->{int i =0 ;while(i<100)System.out.println("Callable线程1在执行:"+i++);return "线程1执行完了";});FutureTask<String> futureTask2 = new FutureTask<>(()->{int i =0 ;while(i<100)System.out.println("Callable线程2在执行:"+i++);return "线程2执行完了";});new Thread(futureTask).start();new Thread(futureTask2).start();System.out.println(futureTask.get());System.out.println(futureTask2.get());}}

run方法才是多线程的执行地方,还是一样我们还是从run方法开始看,我们点击下new Thread会进入Thread的构造器,发现进入的构造器和第二种是一样的:
在这里插入图片描述
这就说明FutureTask一定实现了或者继承了Runnable接口,其实FutureTask实现了RunnableFuture接口,而RunnableFuture继承了Runnable接口,因为继承和实现的特性,相当于FutureTask实现了Runnable接口。那走到这个构造器就是理所当然那的了。所以与第二种方式相同的是Thread调用的target.run就是FutureTask的run了。我们来看下FutureTask的run方法吧:

public void run() {if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;setException(ex);}if (ran)set(result);}} finally {// runner must be non-null until state is settled to// prevent concurrent calls to run()runner = null;// state must be re-read after nulling runner to prevent// leaked interruptsint s = state;if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}}

上面是FutureTask的run方法,里面真正调用的是c.call方法,看到call方法应该就明白了,不错这个call就是我们传入到FutureTask中的Callable实例的call方法,所以FutureTask的调用路线也就清晰了:Thread.start–>Thread.run–>FutureTask.run–>Callable.call。而FutureTask则是Runnable的子类,所以也证明了我们一开始说的java是通过Thread+Runnable来实现的多线程。此外通过这里我们还可以清洗的看到FutureTask是怎么实现参数返回接收的,就是因为call方法有返回,然后FutureTask的run方法接收到返回后将他存放到自身的泛型V中,然后我们就可以直接通过FutureTask.get方法进行获取了。其实这种思想java里有很多,比如常见的http请求的包装类也是通过这种思想来处理流数据的。

👨‍🎓四、通过线程池创建

线程池这里咱们采用自己实现的线程池来进行解释说明,其实没啥区别,只不过是我们一般使用线程池都是禁止直接使用jdk自带线程池的所以才有用自己实现的线程池来看这个问题

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,1, 60,TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy() );threadPoolExecutor.execute(()->{while(true){System.out.println("线程4执行中");}});

关于各个参数的意思,这里就不解释了,需要的看这里:4万字爆肝总结java多线程知识点,言归正传,我们看下线程池是怎么创建线程的,线程池提交任务有submit和execute还有一个定时的schedule,不过schedule他的线程池类不是这个,不过原理一样这里只介绍ThreadPoolExecutor的。我们看下execute的实现方法

public void execute(Runnable command) {if (command == null)throw new NullPointerException();// 此处省略原文注释int c = ctl.get();if (workerCountOf(c) < corePoolSize) {//判断线程是否小于核心线程数,很明显第一次会进入这里if (addWorker(command, true))return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);}

很明显第一次执行会执行:addWorker(command, true)这个方法,我们再看下这个方法做了什么

private boolean addWorker(Runnable firstTask, boolean core) {retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;for (;;) {int wc = workerCountOf(c);if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get();  // Re-read ctlif (runStateOf(c) != rs)continue retry;// else CAS failed due to workerCount change; retry inner loop}}boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {w = new Worker(firstTask);//这个firstTask实际就是我们从execute传入的Runnable实现类final Thread t = w.thread;//获取thread,因为线程开启必须利用Thread的start方法,这两步就是最关键的地方if (t != null) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// Recheck while holding lock.// Back out on ThreadFactory failure or if// shut down before lock acquired.int rs = runStateOf(ctl.get());if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {if (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {t.start();//这里真正调用了线程启动workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;}

最关键的两步,笔者在代码中加了注释了,可以看到会将我们传入的Runnable的实现类交给Worker的构造器,那我们看看这个构造器又干了什么:
在这里插入图片描述
可以看到在这个构造器里面,将我们传入的Runnable给到了自己的Runnable,对他的私有变量进行了初始化,然后又对thread进行了初始化,而初始化Thread时传入了this,注意传入的是this。因为Worker实现了Runable,所以当我们真正执行start方法时,调用的应该是Worker的run方法,所以我们到这里应该去看run方法了,run方法很简单,他直接调用了runWorer方法,那看下runWorker的实现:

final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;//将Runnable的对象指向一个新的引用w.firstTask = null;//失效原引用w.unlock(); // allow interrupts//加锁,worker实现了AQS,支持锁boolean completedAbruptly = true;try {while (task != null || (task = getTask()) != null) {w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted.  This// requires a recheck in second case to deal with// shutdownNow race while clearing interruptif ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);Throwable thrown = null;try {task.run();//这里相当于运行了我们在execute中传入的Runnable对象的run方法了。} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {afterExecute(task, thrown);}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}}

看笔者在代码中加的注释很清晰就看到最终调用的是我们从execute中传入的Runnable的对象的run方法。所以我们看到这里也是可以得出一个解决线程池还是利用Thread+Runnable接口一起实现的多线程。那么来从新梳理下线程池的调用过程:
execute(Runnable)–>
addWorker(command, true)–>内部对Worker(Runnale)进行初始化,Worker该类实现了Runnable,初始化Worker时生产线程Thread传入Worker自己,之后获取上一步生产的thread调用start方法。执行start方法,实际执行的是Worker的run方法,worker.run方法调用了runWorker方法,该方法执行时是将Worker的Runnable的对象的run方法进行调用执行。而Worker的Runnable的对象就是在addWorker中由execute方法传入的Runnable实现类。这样整个流程就很清晰了,我们也是可以佐证一开始说的观点了。

👨‍🎓五、总结

看了以上四种分析,我们可以清晰的发现了java中其实创建线程的方式就只有一种就是利用Thread+Runnable来实现多线程。其他多有方式都是对这个实现方式的变种。如有不对欢迎路过的朋友指正,也希望能帮到路过的朋友

在这里插入图片描述


http://chatgpt.dhexx.cn/article/n26GAnI7.shtml

相关文章

java中创建线程的4种方式

写在前面的话 java线程创建方式有几种&#xff1f;这种问题在面试中经常被问到&#xff0c;你可能心里马上反映出两种方式&#xff08;实现Runnable、继承Thread&#xff09;&#xff0c;当你把这两种叙述给面试官听后&#xff0c;面试官会觉得你该掌握的知识已经有了&#xf…

【 java 多线程】创建多线程的方式四:使用线程池创建多线程

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

创建线程的方式

一般来说&#xff0c;创建线程有三种方式&#xff1a;继承 Thread 类、实现 Runnable 接口和实现 Callable 接口。 继承 Thread 类&#xff1a;继承 Thread 类&#xff0c;重写 run() 方法&#xff0c;调用 start() 方法启动线程。 public class MyThread {public static cla…

【JAVAEE】创建线程的方式及线程的常用方法

目录 1.创建线程的四种方式 1.1继承Thread类 1.2实现Runnable接口 1.3匿名内部类 1.4lambda表达式 2.多线程的优势-增加运行速度 3.Thread类及常用方法 3.1构造方法 3.2常见属性 演示后台线程 演示线程是否存活 3.3线程中断 3.4线程等待-join() 3.5获取当前线程 …

创建线程的方式有那些?

目录 一.创建线程的4种方式 二.创建线程方式有什么区别&#xff1f; 一.创建线程的4种方式 &#xff08;1&#xff09;写一个类继承Thread&#xff0c;覆盖重写run方法 &#xff08;2&#xff09;创建一个Runnable类型的对象,实现run()方法,传入Thread的构造方法中 &#x…

创建线程几种方式

创建线程的几种方式&#xff1a; 方式1&#xff1a;通过继承Thread类创建线程 步骤&#xff1a;1.定义Thread类的子类&#xff0c;并重写该类的run方法&#xff0c;该方法的方法体就是线程需要执行的任务&#xff0c;因此run()方法也被称为线程执行体 2.创建Thread子类的实例&a…

线程创建的四种方式

java中创建线程的四种方法以及区别 Java使用Thread类代表线程&#xff0c;所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程&#xff0c;如下所示&#xff1a; 1&#xff09;继承Thread类创建线程 2&#xff09;实现Runnable接口创建线程 3&am…

四种线程创建方式

一. 继承Thread类 继承Thread类创建线程的步骤: 创建一个自定义类继承Thread类,重写run()方法,将所要单独线程运行写入run()方法中;创建Thread类的子类的对象;调用该对象的start()方法,该start()方法表示开启线程,然后调用执行run方法; Testpublic void test() {Thread.curre…

创建线程的四种方式

我们创建线程池一般有四种方式&#xff0c;分别是&#xff1a; 1.继承Thread类&#xff0c;重写run()方法&#xff1b; 2.实现Runnable接口&#xff0c;重写run()方法&#xff1b; 3.实现Callable接口&#xff0c;重写call()方法&#xff1b; 4.使用线程池创建线程&#xff1b;…

线程创建常用的四种方式

java中创建线程的四种方法以及区别 Java使用Thread类代表线程&#xff0c;所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程&#xff0c;如下所示&#xff1a; 1&#xff09;继承Thread类创建线程 2&#xff09;实现Runnable接口创建线程 3&…

银行测试核心项目之测试阶段分享

最近有小伙伴说「想了解核心系统建设中&#xff0c;冒烟、SIT、UAT、回归测试的重点&#xff0c;如何设计测试案例&#xff0c;或相关的资料推荐等」。 这个话题很笼统&#xff0c;测试这一块儿除了业务测试&#xff0c;还有性能测试、安全测试等&#xff1b;以及不同的角色对…

金融银行测试面试题分享

1、网上银行转账是怎么测的&#xff0c;设计一下测试用例。 回答思路&#xff1a; 宏观上可以从质量模型&#xff08;万能公式&#xff09;来考虑&#xff0c;重点需要测试转账的功能、性能与安全性。设计测试用例可以使用场景法为主&#xff0c;先列出转账的基本流和备选流。…

2022年软件测试——精选金融银行面试真题

前言 小伙伴们好久不见呀&#xff0c;现已经到了八月份了过了这一个月就是金九银十了&#xff0c;在这里呢笔者给大家准备了一份软件测试金融方面的面试题&#xff0c;笔者在这里就不多说废话了哟&#xff0c;咱们直接进入正题哈。 1、P2P你们也测试后台管理吗&#xff1f;个人…

金融行业软件测试

金融行业软件测试 一、什么是金融行业二、金融行业的业务特点1. 金融行业的业务特点 三、金融行业测试主要测试范围1. 功能测试2. 业务验证测试3. 客户端测试4. 接口测试5. 性能测试6. 安全性测试 四、金融行业软件测试的现状五、第三方测试的优点 一、什么是金融行业 金融业是…

走出新手村,软件测试银行项目怎么测试 + 面试题(答案)

前言 业务&#xff1a; 银行类app具体模块业务讲解&#xff1a; 掌上生活&#xff08;消费/理财&#xff09; 消费&#xff1a; 与电商平台业务类似 饭票&#xff1a;GPS定位城市服务、饭票购买消费、餐厅查询、代金券、周三五折、banner图、我的饭票、我的抵扣券 影票 …

【工作总结】银行软件测试工作总结

最近换工作进入到外包公司&#xff0c;驻点到银行进行集成测试工作&#xff0c;为了让自己更熟悉金融行业&#xff0c;特别是银行业相关业务&#xff0c;以及总结自己测试的一些经验&#xff0c;希望将自己了解和学习的内容记录下来。主要从以下几个方面学习&#xff1a; 一、…

大咖or学员?金融银行软件测试案例分享!

2019年4月14日汇智动力学院在成都校区开展了本年度的第一次线下技术交流沙龙&#xff08;银行金融测试方向&#xff09;&#xff1b; 刘德宝老师做技术分享 通过在上个月末举办的老学员周末娱乐活动&#xff08;雀圣大赛&#xff09;&#xff0c;我们做了一系列的问卷调查&…

银行软件测试面试问题

测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 参考答案&#xff1a; 兼容测试主要是检查软件在不同的硬件平台、软件平台上是否可以正常的运行&#xff0c;即是通常说的软件的可移植性。 兼容的类型&#xff0c;如果细分的话&#x…

浦发银行软件测试面试真题(小编面试亲测)

对于想要面试去银行面试的小伙伴们看过来&#xff0c;今天为大家总结了浦发银行的面试题&#xff0c;其实很多面试题很多公司都差不多。主要看自己如何灵活回答。以下的问题小编盆友之前面试非浦发银行的银行项目也有被问到过&#xff0c;是在一面的时候被问到的。答案仅作为参…

金融银行软件测试超大型攻略,最受欢迎的金融银行大揭 秘附面试题

零、为什么做金融类软件测试 举个栗子&#xff0c;银行里的软件测试工程师。横向跟互联网公司里的测试来说&#xff0c;薪资相对稳定&#xff0c;加班少甚至基本没有&#xff0c;业务稳定。实在是测试类岗位中的香饽饽&#xff01; 一、什么是金融行业 金融业是指经营金融商…