Java并发编程—实现线程的方式只有一种

实现 Runnable 接口


class MyThread implements Runnable { // 定义线程主体类

private String name; // 定义类中的属性

public MyThread(String name) { // 定义构造方法

this.name = name;

}

@Override

public void run() { // 覆写run()方法

for (int x = 0; x < 200; x++) {

System.out.println(this.name + " --> " + x);

}

}

}

首先通过 MyThread 类实现 Runnable 接口,然后重写 run() 方法,之后只需要把这个实现了 run() 方法的 MyThread 实例传到 Thread 类中就可以实现多线程。

如何运行Runnable线程:

MyThread a = new MyThread();

new Thread(a).start();

继承 Thread 类


class MyThread extends Thread { // 这就是一个多线程的操作类

private String name ; // 定义类中的属性

public MyThread(String name) { // 定义构造方法

this.name = name ;

}

@Override

public void run() { // 覆写run()方法,作为线程的主操作方法

for (int x = 0 ; x < 200 ; x ++) {

System.out.println(this.name + " --> " + x);

}

}

}

与第 1 种方式不同的是它没有实现接口,而是继承 Thread 类,并重写了其中的 run() 方法。相信上面这两种方式你一定非常熟悉,并且经常在工作中使用它们。

由Thread类的定义可知Thread类也是Runnable接口的子类:

public class Thread extends implements Runnable

因而启动一个Thread类线程有两种方式:

new MyThread().start();

MyThread a = new MyThread();

new Thread(a).start();

需要更多大厂面试资料的话也可以点击直接进入,免费获取!暗号:CSDN

有返回值的 Callable 创建线程


先说一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法:

public interface Runnable {

public abstract void run();

}

无论是使用Thread还是Runnable都无法返回值,这一点是它们的共同缺点。JDK1.5之后提出了Callable。

  1. Callable接口更像是Runnable接口的增强版,相比较Runable接口,Call()方法新增捕获和抛出异常的功能;Call()方法可以返回值

  2. Future接口提供了一个实现类FutureTask实现类,FutureTaks类用来保存Call()方法的返回值,并作为Thread类的target。

  3. 调用FutureTask的get()方法来获取返回值

class CallableTask implements Callable {

@Override

public Integer call() throws Exception {

return new Random().nextInt();

}

}

但是在Thread类中并没有接受Callable实例对象的方式,在实现Callable之后需要借助FutureTask类,在JDK1.5之后Java提供了java.util.concurrent.FutureTask。

来看看FutureTask的两种构造方法:

public FutureTask(Callable callable)

public FutureTask(Runnable runnable,V result)

其基本的类继承结构如图所示,可以发现FutureTask实现了RunnableFuture接口,而RunnableFuture又实现了Future和Runnable接口

在这里插入图片描述

所以无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,是需要被执行的,而不是说它们本身就是线程。它们可以放到线程池中执行,不管用什么方法,最终都是靠线程来执行的,而子线程的创建方式仍脱离不了最开始讲的两种基本方式,也就是实现 Runnable 接口和继承 Thread 类。

//创建线程池

ExecutorService service = Executors.newFixedThreadPool(10);

//提交任务,并用 Future提交返回结果

Future future = service.submit(new CallableTask());

线程池创建线程


线程池确实实现了多线程,比如我们给线程池的线程数量设置成 10,那么就会有 10 个子线程来为我们工作,接下来,我们深入解析线程池中的源码,来看看线程池是怎么实现线程的?

static class DefaultThreadFactory implements ThreadFactory {

DefaultThreadFactory() {

SecurityManager s = System.getSecurityManager();

group = (s != null) ? s.getThreadGroup() :

Thread.currentThread().getThreadGroup();

namePrefix = “pool-” +

poolNumber.getAndIncrement() +

“-thread-”;

}

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;

}

}

对于线程池而言,本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFactory ,它会给线程池创建的线程设置一些默认值,比如:线程的名字、是否是守护线程,以及线程的优先级等。但是无论怎么设置这些属性,最终它还是通过new Thread()创建线程的 ,只不过这里的构造函数传入的参数要多一些,由此可以看出通过线程池创建线程并没有脱离最开始的那两种基本的创建方式,因为本质上还是通过 new Thread() 实现的。

所以我们在回答线程实现的问题时,描述完前两种方式,可以进一步引申说“我还知道线程池和Callable 也是可以创建线程的,但是它们本质上也是通过前两种基本方式实现的线程创建。”这样的回答会成为面试中的加分项。

需要更多大厂面试资料的话也可以点击直接进入,免费获取!暗号:CSDN

总结1:实现线程只有一种方式


关于这个问题,我们先不聚焦为什么说创建线程只有一种方式,先认为有两种创建线程的方式,而其他的创建方式,比如线程池或是定时器,它们仅仅是在 new Thread() 外做了一层封装,如果我们把这些都叫作一种新的方式,那么创建线程的方式便会千变万化、层出不穷,比如 JDK 更新了,它可能会多出几个类,会把 new Thread() 重新封装,表面上看又会是一种新的实现线程的方式,透过现象看本质,打开封装后,会发现它们最终都是基于 Runnable 接口或继承 Thread 类实现的。

总结2:实现 Runnable 接口比继承 Thread 类实现线程要好


下面我们来对刚才说的两种实现线程内容的方式进行对比,也就是为什么说实现 Runnable 接口比继承 Thread 类实现线程要好?好在哪里呢?

  1. 实现 Runnable 与 Thread 类的解耦。Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,Thread 类负责线程启动和属性设置等内容,权责分明。

  2. 提高性能。使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程,如果还想执行这个任务,就必须再新建一个继承了 Thread 类的类,整个线程从开始创建到执行完毕被销毁,这一系列的操作比 run() 方法打印文字本身带来的开销要大得多,相当于捡了芝麻丢了西瓜,得不偿失。如果我们使用实现 Runnable 接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性能开销。

  3. Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。

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

总结3:为什么多线程启动不是调用run()而是start()


最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
img-5qE2lCED-1715737328151)]

[外链图片转存中…(img-L8SwTmQt-1715737328152)]

[外链图片转存中…(img-8I2vsrQj-1715737328152)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值