【Java面试题】线程创建的三种方式及区别?

三种线程创建方式

  1. 继承Thread类,子类重写run()方法,调用子类的strat()启动线程。
  2. 实现Runnable接口,实现run()方法,调用对象start()启动线程。
  3. 实现Callable接口,实现call()方法,用FutureTask()封装实现类。使用FutureTask对象作为Thread对象调用start()启动线程,调用FutureTask对象的get()方法获取返回值()。

三种方式的优缺点 

采用继承Thread类方式:

  • 优点:编写简单。
  • 缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。

采用实现Runnable接口方式:

  • 优点:线程类只是实现了Runable接口,还可以继承其他的类。
  • 缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

Runnable和Callable的区别:

  • Callable规定的方法是call(),Runnable规定的方法是run()。
  • Callable的任务执行后可返回值,而Runnable的任务是不能返回值得。
  • Call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常
  • 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

总结:Runnable和Callable功能一样的,都是构造线程执行的任务;其区别可以简单理解为有无返回值的区别,通常Callable使用的比较多

继承Thread类

继承Thread类的话,必须重写run方法,在run方法中定义需要执行的任务。

class MyThread extends Thread{private static int num = 0;public MyThread(){num++;}@Overridepublic void run() {System.out.println("主动创建的第"+num+"个线程");}}

创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。

public class Test {public static void main(String[] args)  {MyThread thread = new MyThread();thread.start();}}class MyThread extends Thread{private static int num = 0;public MyThread(){num++;}@Overridepublic void run() {System.out.println("主动创建的第"+num+"个线程");}}

在上面代码中,通过调用start()方法,就会创建一个新的线程了。为了分清start()方法调用和run()方法调用的区别,请看下面一个例子:

public class Test {public static void main(String[] args)  {System.out.println("主线程ID:"+Thread.currentThread().getId());MyThread thread1 = new MyThread("thread1");thread1.start();MyThread thread2 = new MyThread("thread2");thread2.run();}}class MyThread extends Thread{private String name;public MyThread(String name){this.name = name;}@Overridepublic void run() {System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());}}

运行结果:

主线程ID:1
name:thread2 子线程ID:1
name:thread1 子线程ID:8

从输出结果可以得出以下结论:

1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;

2)虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。

2.实现Runnable接口 

  1. 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 
  2. 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 
  3. 调用线程对象的start()方法来启动该线程。
package Thread;import java.util.concurrent.*;
//测试类
public class TestThread {public static void main(String[] args) throws Exception {testImplents();}public static void testImplents() throws Exception {MyThreadImplements myThreadImplements = new MyThreadImplements();Thread t1 = new Thread(myThreadImplements);Thread t2 = new Thread(myThreadImplements, "my thread -2");t1.start();t2.start();}
}
//线程类
class MyThreadImplements implements Runnable {@Overridepublic void run() {System.out.println("通过实现Runable,线程号:" + Thread.currentThread().getName());}
}

3. 使用Callable接口

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

创建并启动有返回值的线程的步骤如下:

  1. 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
  3. 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

下面是一个例子:

public class Main {public static void main(String[] args){MyThread3 th=new MyThread3();//使用Lambda表达式创建Callable对象//使用FutureTask类来包装Callable对象FutureTask<Integer> future=new FutureTask<Integer>((Callable<Integer>)()->{return 5;});new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程try{System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回}catch(Exception e){ex.printStackTrace();}}}

start()和run()的区别?

(具体区别可以看【Java面试题】线程中start方法和run方法的区别?

  • start()方法用来,开启线程,但是线程开启后并没有立即执行,他需要获取cpu的执行权才可以执行
  • run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)

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

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

相关文章

【回味“经典”】DFS基础训练(N皇后,装载问题)

这篇文章是一年前写的 走进“深度搜索基础训练“&#xff0c;踏入c算法殿堂&#xff08;一&#xff09;和 走进“深度搜索基础训练“&#xff0c;踏入c算法殿堂&#xff08;二&#xff09;的重编版。 希望以此&#xff0c;唤起对那位故人的回忆。 小航走入赛场&#xff0c;比赛…

常见的网络设备有哪些?分别有什么作用?

个人主页&#xff1a;insist--个人主页​​​​​​ 本文专栏&#xff1a;网络基础——带你走进网络世界 本专栏会持续更新网络基础知识&#xff0c;希望大家多多支持&#xff0c;让我们一起探索这个神奇而广阔的网络世界。 目录 一、网络设备的概述 二、常见的网络设备 1、…

eslintignore无效解决办法

项目的根目录下新建.eslintignore&#xff0c;但是无论怎么配置&#xff0c;该文件总是无法生效。本想解决不生效的问题&#xff0c;但是一直无法解决&#xff0c;于是换了一种解决问题的思路。 方法一&#xff1a; 在需要进行忽略的文件顶部加上 /* eslint-disable */这样e…

JVS低代码中表单引擎与逻辑引擎是如何联合调用外部API的?

在企业项目中&#xff0c;常常出现需要给外部系统提供一个api &#xff0c;让外部系统触发调用&#xff0c;本系统直接数据入库&#xff0c;那么我们来看看jvs的表单引擎与逻辑引擎联合实现这个功能&#xff0c;先看实现效果&#xff1a; 配置步骤&#xff1a; 一、配置列表页…

【机密计算实践】支持 Intel SGX 的 LibOS 项目介绍(一)

一、LibOS 库操作系统(Library Operating System,简称 LibOS)是根据某类应用的特殊需求,由某一高级编程语言将原本属于操作系统内核的某些资源管理功能,如文件磁盘 I/O、网络通信等,按照模块化的要求,以库的形式提供给应用程序的特殊操作系统。 它能代替操作系统内核合…

springboot(JavaCV )实现视频截取第N帧并保存图片

springboot&#xff08;JavaCV &#xff09;实现视频截取第N帧并保存图片 现在视频网站展示列表都是用img标签展示的&#xff0c;动图用的是gif&#xff0c;但是我们上传视频时并没有视屏封面&#xff0c;就这需要上传到服务器时自动生成封面并保存 本博客使用jar包的方式实现…

如何成功开展跨境电子商务?快速入门!

随着全球化的推进和互联网技术的发展&#xff0c;跨境电子商务已经成为许多企业追求新市场和实现增长的重要途径。然而&#xff0c;要在这个竞争激烈的领域中脱颖而出并取得成功并非易事。本文将介绍三个可行的策略&#xff0c;以帮助企业成功开展跨境电子商务。 第一策略&…

浙大数据结构第八周之08-图8 How Long Does It Take

前置知识&#xff1a; 拓扑排序&#xff1a; /* 邻接表存储 - 拓扑排序算法 */bool TopSort( LGraph Graph, Vertex TopOrder[] ) { /* 对Graph进行拓扑排序, TopOrder[]顺序存储排序后的顶点下标 */int Indegree[MaxVertexNum], cnt;Vertex V;PtrToAdjVNode W;Queue Q Cre…

网络面试题(172.22.141.231/26,该IP位于哪个网段? 该网段拥有多少可用IP地址?广播地址是多少?)

此题面试中常被问到&#xff0c;一定要会172.22.141.231/26&#xff0c;该IP位于哪个网段&#xff1f; 该网段拥有多少可用IP地址&#xff1f;广播地址是多少&#xff1f; 解题思路&#xff1a; 网络地址&#xff1a;172.22.141.192 10101100.00010110.10001101.11000000 广播…

【后端速成 Vue】第一个 Vue 程序

1、为什么要学习 Vue&#xff1f; 为什么使用 Vue? 回想之前&#xff0c;前后端交互的时候&#xff0c;前端收到后端响应的数据&#xff0c;接着将数据渲染到页面上&#xff0c;之前使用的是 JavaScript 或者 基于 JavaScript 的 Jquery&#xff0c;但是这两个用起来还是不太…

uni-app 打包生成签名Sha1

Android平台打包发布apk应用&#xff0c;需要使用数字证书&#xff08;.keystore文件&#xff09;进行签名&#xff0c;用于表明开发者身份。 可以使用JRE环境中的keytool命令生成。以下是windows平台生成证书的方法&#xff1a; 安装JRE环境&#xff08;推荐使用JRE8环境&am…

yolov8模型转onnx模型 和 tensorRT 模型

转onnx模型 在 安装好 pip install onnxruntime-gpu pip install onnx onnxconverter-common 出现 No module named cpuinfo 错误&#xff0c;通过安装&#xff1a; pip install py-cpuinfo 解决该问题。 import sys # 即 ultralytics文件夹 所在绝对路径 sys.path.app…

STM32 GPIO复习

GPIO General Purpose Input Output&#xff0c;即通用输入输出端口&#xff0c;简称GPIO。 负责采集外部器件的信息或控制外部器件工作&#xff0c;即输入输出。 不同型号&#xff0c;IO口数量可能不一样&#xff0c;可通过选型手册快速查询。 能快速翻转&#xff0c;每次翻…

Crimson:高性能,高扩展的新一代 Ceph OSD

背景 随着物理硬件的不断发展&#xff0c;存储软件所使用的硬件的情况也一直在不断变化。 一方面&#xff0c;内存和 IO 技术一直在快速发展&#xff0c;硬件的性能在极速增加。在最初设计 Ceph 的时候&#xff0c;通常情况下&#xff0c;Ceph 都是被部署到机械硬盘上&#x…

vellum (Discovering Houdini VellumⅡ柔体系统)学习笔记

视频地址&#xff1a; https://www.bilibili.com/video/BV1ve411u7nE?p3&spm_id_frompageDriver&vd_source044ee2998086c02fedb124921a28c963&#xff08;搬运&#xff09; 个人笔记如有错误欢迎指正&#xff1b;希望可以节省你的学习时间 ~享受艺术 干杯&#x1f37b…

Vue——如何在安卓项目中加载离线vue项目

最近在做一个离线工单的功能&#xff0c;为了直接复用原来在线H5的代码&#xff0c;我希望将它放到安卓本地来加载&#xff0c;做法比较简单&#xff0c;无非就是npm run build打包&#xff0c;然后把包放到安卓项目的assets目录下&#xff0c;然后按照正常加载webview的方式加…

jira增删改查接口

安装 pip install atlassian-python-api3.40.1 若安装失败,可尝试加上清华源(-i https://pypi.tuna.tsinghua.edu.cn/simple) 使用 为了防止信息泄露&#xff0c;可将账号密码单独存放到json文件中 &#xff0c;如credential.json {"name" : "xiaoming"…

python常用

环境配置 conda Conda自动补全 在终端激活conda环境的时候按tab不能自动补全activate和环境名。安装后可用tab进行补全。 安装 conda-bash-completion 插件&#xff1a;GitHub 安装方法&#xff1a; conda install -c conda-forge conda-bash-completion常用命令 #创建虚拟…

通过几段代码,详解Python单线程、多线程、多进程

在使用爬虫爬取数据的时候&#xff0c;当需要爬取的数据量比较大&#xff0c;且急需很快获取到数据的时候&#xff0c;可以考虑将单线程的爬虫写成多线程的爬虫。下面来学习一些它的基础知识和代码编写方法。 一、进程和线程 进程可以理解为是正在运行的程序的实例。进程是拥…