Java之多线程的实现(创建)(3种实现方式)(面试高频)

目录

一、多线程的3种实现方式

(1)继承Thread类。

(2)实现Runnable接口。(void run():该方法无返回值、无法抛出异常)

(3)实现Callable接口。(V call() throws Exception:该方法可以返回结果、可以抛出异常)

(4)如何选择?

<1>选择继承Thread类?

<2>实现Runnable接口。

<3>实现Callable接口。

二、代码演示3种实现情形。

(1)继承Thread类创建多线程。

<1>学习多线程先看一个案例。

<2>问题的分析。

<3>改进。继承Thread类。重写run()方法。在run()方法中写while()循环。

<4>总结。

(2)实现Runnable接口创建多线程。

<1>分析。

<2>代码改进。

<3>测试结果。

(3)实现Callable接口创建多线程。

<0>大致实现步骤。(实现Callable接口满足既能创建新线程又能有返回值的需求)

<1>书上的案例代码示例。解释在注释中。

<2>自己的案例。

<3>接着。任务类"CalcTask"(执行累加操作)

<4>接着。测试类"Test"(main方法中创建、启动线程)

(I)Future接口。

(II)RunnableFuture接口的实现子类"FutureTask"。

(III)FutureTask实现类。

(IIII)举例类似的阻塞方法。

(IIIII)代码。


一、多线程的3种实现方式

(1)继承Thread类。
  • 继承java.lang包中的Thread类,重写Thread类的run()方法,在run()方法中实现多线程的代码。
  • Thread类是Runnable接口的一个实现类。
  • 文心一言说:Thread类本身是一个类,它并不直接实现Runnable接口。但它有一个成员变量target,这个成员变量的类型是Runnable。new Thread(Runnable target)

(2)实现Runnable接口。(void run():该方法无返回值、无法抛出异常
  • 实现java.lang.Runnable接口。在run()方法中实现多线程的代码。
  • 也就是如果它中间有异常,无法抛出异常。只能在run()方法中进行异常处理。不能去线程外部进行处理异常。
(3)实现Callable接口。(V call() throws Exception:该方法可以返回结果、可以抛出异常
  • 实现java.util.concurrent.Callable接口,重写call()方法,并使用Future接口获取call()方法的返回的结果。
  • 在实际开发中,虽然异常是一种不好的错误信息。但是它可以作为一种信息返回到外界。
  • 比如线程在执行过程,我想要把线程所执行的结果返回。像12306的多座位类型分配座位,开一个线程:分配商务座、其它座,线程执行完成之后,将抢到的的座位信息返回给主线程。
  • 线程启动比实现Runnable接口还要麻烦一点。就是在启动线程的时候要依赖Thread类,但又不能直接传给Thread类,得借助于另外一个类的对象。后面详细介绍。

(4)如何选择?
<1>选择继承Thread类?
  • 启动线程代码简单。但是创建的线程类不能再继承其他类,少用。因为程序的扩展性降低。
  • 通过继承Thread类实现了多线程,因为有局限性。因为Java只支持单继承,一个类一旦继承了某个父类,就无法再继承Thread类了。如:Student类继承Person类,那么Student类无法再通过继承Thread类创建线程。
  • 但是平时的学习、简单的测试比较方便使用。
<2>实现Runnable接口。
  • 为了克服上面方法的弊端——>Thread类提供一个构造方法——new Thread(Runnable target),其中参数Runnable是一个接口,它只有一个run()方法。
  • 这样我们的线程类可以再去继承其他类。但是启动线程的码较复杂,依赖Thread类,调用Thread类的start()。还是比较推荐该方法。保留了程序可扩展性。

<3>实现Callable接口。
  • 通过Thread类和Runnable接口实现多线程时,需要重写run()方法,但是由于run()方法没有返回值,无法从新线程中获取返回结果。Java提供了Callable接口来满足这种既能创建新线程又有返回值的需求
  • 因为call()方法有返回值且可抛出异常。可根据实际情况进行选择。比较推荐

二、代码演示3种实现情形。

(1)继承Thread类创建多线程。

(本模块直接按照上课书本《Java基础入门》来举例)

<1>学习多线程先看一个案例。
  • 测试类Example02。main方法中创建Thread01类的对象,调用run()方法,执行while()循环输出"Thread01的run()方法正在执行",接着主线程main也写一个while()循环去打印"main()方法的run()方法正在执行"。

  • 测试类代码。
/*** @Title: Example01* @Author HeYouLong* @Package PACKAGE_NAME* @Date 2024/10/26 下午10:58* @description: 测试类*/
public class Example01 {public static void main(String[] args) {MyThread01 myThread = new MyThread01();myThread.run();while (true){System.out.println("main()方法在运行!");}}
}
//第二个类不能用修饰符
class MyThread01 {public void run(){while(true){System.out.println("MyThread类的run()方法正在运行!");}}
}
  • 测试代码的运行结果。


<2>问题的分析。
  • 这里可以看到程序一直在打印"MyThread类的run()方法正在运行!"这是因为该程序是一个单线程程序
  • 代码中调用MyThread01类的run()方法时,执行里面的死循环,因此MyThread01类的println语句一直在执行!而main()方法当中println语句一直无法得到执行。
  • 如果希望上面程序的两个死循环while()中的println语句能够并发的执行,就需要实现多线程
<3>改进。继承Thread类。重写run()方法。在run()方法中写while()循环。
  • 测试类代码。
/*** @Title: Example01* @Author HeYouLong* @Package PACKAGE_NAME* @Date 2024/10/26 下午10:58* @description: 测试类*/
public class Example02 {public static void main(String[] args) {MyThread02 myThread = new MyThread02();  //创建MyThread02类的线程对象myThread.start();  //开启线程while (true){   //死循环输出信息System.out.println("main()方法在运行!");}}
}
//第二个类不能用修饰符
class MyThread02 extends Thread {@Overridepublic void run() {while (true){   //死循环输出信息System.out.println("MyThread类的run()方法正在运行!");}}
}
  • 测试代码的运行结果。


<4>总结。
  • 上面代码利用两个线程模拟多线程环境。
  • 在main()方法当中创建了MyThread02类的线程对象myThread。并且通过myThread对象调用start()方法启动新线程。
  • 单线程程序在运行时,会按照代码的调用顺序执行。
  • 多线程程序中,main()方法和MyThread类的run()方法可以同时运行,互不影响

(2)实现Runnable接口创建多线程。
<1>分析。
  • 提供Thread类提供的构造方法——new Thread(Runnable target)。
  • 当Thread(Runnable target)构造方法去创建线程对象时,只需要为该方法传递"一个已经实现了Runnable接口的对象"。
  • 然后创建的线程将实现Runnable接口中的run()方法作为运行代码,而不需要调用Thread类中的run()方法
<2>代码改进。
/*** @Title: Example01* @Author HeYouLong* @Package PACKAGE_NAME* @Date 2024/10/26 下午10:58* @description: 测试类*/
public class Example03 {public static void main(String[] args) {MyThread03 myThread = new MyThread03();  //创建MyThread03类的线程对象,并且该类已经实现Runnable接口Thread thread = new Thread(myThread);  //创建线程对象thread.start();  //开启线程,执行线程中的run()方法。while (true){   //死循环输出信息System.out.println("main()方法在运行!");}}
}
//第二个类不能用修饰符
class MyThread03 implements Runnable {@Overridepublic void run() {while (true){   //死循环输出信息System.out.println("MyThread类的run()方法正在运行!");}}
}
<3>测试结果。


(3)实现Callable接口创建多线程。
<0>大致实现步骤。(实现Callable接口满足既能创建新线程又能有返回值的需求
  • 创建Callable接口的实现类,同时重写Callable接口的call()方法。
  • 创建Callable接口的实现类对象。
  • 通过线程处理类FutureTask的有参构造方法封装Callable接口的实现类对象。
  • 调用参数为FutureTask类对象的有参构造方法Thread()创建Thread线程实例。
  • 调用线程实例的start()方法启动线程。

<1>书上的案例代码示例。解释在注释中。
  • 代码。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** @Title: Example01* @Author HeYouLong* @Package PACKAGE_NAME* @Date 2024/10/26 下午10:58* @description: 测试类*/
public class Example04 {public static void main(String[] args) throws ExecutionException, InterruptedException {MyThread04 myThread = new MyThread04();  //创建MyThread04类的线程对象,并且该类已经实现Callable接口//使用Future接口的实现子类封装MyThread04类FutureTask<Object> futureTask = new FutureTask<>(myThread);//通过Thread(Runnable target,String name)构造方法创建线程对象Thread thread = new Thread(futureTask,"线程1");  //创建线程对象thread.start();  //开启线程,执行线程中的run()方法。//通过get()方法获取任务类FutureTask的执行结果(call()方法)System.out.println(Thread.currentThread().getName()+"返回的结果:i ="+futureTask.get());int a=0;while(a++ < 5){System.out.println("main()方法正在运行!");}}
}
//第二个类不能用修饰符
//定义一个实现Callable接口的实现类
class MyThread04 implements Callable<Object> {@Overridepublic Object call() throws Exception {int i =0;while (i++ <5){System.out.println(Thread.currentThread().getName()+"的call()方法正在运行!");}return i;}
}
  • 运行结果。

<2>自己的案例。
  • 使用一个线程执行累加操作。执行完后需要把累加的结果返回。
  • 通过主线程调用,让某一个线程做累加操作。主线程不会去等待它,等它执行完操作,将返回的值返回回来并拿到它。

  • 让任务类实现Callable接口,并重写call()方法。
  • 因为累加操作。所以指定泛型为:Integer。

  • 如何封装Callable接口类型的数据,然后调用Thread类的构造方法去创建、启动线程?
<3>接着。任务类"CalcTask"(执行累加操作)
  • 当线程启动,会自己调用重写的call()方法。进行累加操作。
  • 通过线程睡眠,模拟执行操作后返回结果需要的时间。
package com.fs;import java.util.concurrent.Callable;/*** @description: 累加任务*/
/* 使用一个线程执行累加操作。*/
public class CalcTask implements Callable<Integer> {//重写call()方法@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i < 100; i++) {sum += i;//线程睡眠,模拟处理消耗的时间Thread.sleep(10);}return sum;}
}
<4>接着。测试类"Test"(main方法中创建、启动线程)
  • 这里实现Callable接口——>创建、启动线程的方法要依赖于Thread类
  • 如果是实现Runnable接口,就是可以这样进行如下操作(new Thread(Runnable target))。但是它不行,newThread(参数),参数没有提供。
  • 在Thread构造方法当中,它没办法接收Callable接口类型的数据,但可以接收Runnable接口类型的数据。所以还得往下面看。

(I)Future接口。
  • 表示一个未来的。它的作用就是包裹一个"未来的结果"。比如Callable接口通过线程启动完成,执行call()方法就会返回一个结果。Future是一个接口,肯定不能new去使用它,但是它有实现类

  • Future接口的子接口。
  • ScheduledFuture接口(定时任务、任务调度)、RunnableFuture接口(继承Runnable、Future接口)

(II)RunnableFuture接口的实现子类"FutureTask"。
  • RunnableFuture接口。它的实现类就是我们这次要用的类"FutureTask"。它的父类是Runnable是子接口,那么它就是相当于一个"孙子类"。

(III)FutureTask实现类。
  • 它有两个构造方法。一个可以传一个Callable。另外一个可以传一个Runnale。

  • 其中它的get()方法是一个"阻塞"方法。也就是某个调用call()方法的线程没有执行完,它会一直等着,直到拿到返回的对应接口类型的值。
  • 其次这个get()方法还有一个有参数的,它就是如果等待的时间过长(如果不处理,可能导致"死锁"产生),就会抛出异常。

  • 这里就可以相当于join()方法。之前《龟兔赛跑》时,主线程调用join()方法,等待"兔子线程"、"乌龟线程"执行完了后,再统计结果。而这里使用,就可以用get()方法等待两个线程执行完并获取到返回值,再统计结果。
  • 取消方法。其实就是执行call()方法时,不想执行了。就可以让线程调用cancel()方法,让它停止执行call()方法了。

(IIII)举例类似的阻塞方法。
  • Scanner类的next()方法。同样等着输入回车后,程序才向下执行。Scanner的底层也是是输入流。
  • IO流的read()方法。
(IIIII)代码。
  • 启动线程后,会等待运行一段时间才有结果。
  • 因为即使main线程抢到时间片,而get()方法将这里"阻塞"。直到Thread01执行完call()方法并拿回结果"sum",才继续往下执行。

package com.fs;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** @Title: Test* @Author HeYouLong* @Package com.fs* @Date 2024/10/13 下午4:53* @description: 测试类*/
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建任务。创建、启动线程CalcTask calcTask = new CalcTask();/** 将已经实现Callable接口的任务类封装起来* 再使用newThread()* *///创建一个FutureTask 对象包含callable的对象,FutureTask<Integer> futureTask = new FutureTask<>(calcTask);Thread thread01 = new Thread(futureTask);//调用get()方法获取Callable线程的call()方法返回结果。// get()阻塞的,如果callable线程的call()方法没有执行完成,一直在等待,call()方法执行完毕//线程启动,自己会调用call()方法thread01.start();//通过调用get()方法拿到结果Integer rs = futureTask.get();System.out.println("main线程执行,rs:"+rs);}
}


  • 测试。我们让main线程只等100毫秒。而我们的call()方法需要执行10*100=1000毫秒。现在在启动再看看结果。
  • 稍微修改一下get()方法。
//通过调用get()方法拿到结果//MILLISECONDS:毫秒Integer rs = futureTask.get(100, TimeUnit.MILLISECONDS);//记得处理异常或者抛出
  • 如果到达超时时间,还没有得到结果,抛TimeoutException异常。

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

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

相关文章

企业如何吸引稀缺的高技能员工

高技能员工的稀缺性和招聘难度日益凸显&#xff0c;其原因主要在于技术发展迅速、人才供需失衡、企业竞争加剧。其中&#xff0c;技术发展迅速导致人才培养跟不上市场需求&#xff0c;使得高技能人才更加稀缺。以人工智能领域为例&#xff0c;新技术层出不穷&#xff0c;相关人…

【MySQL】MySQL数据库中密码加密和查询的解决方案

本篇博客是为了记录自己在遇到password函数无法生效时的解决方案。通过使用AES_ENCRYPT(str,key)和AES_DECRYPT(str,key)进行加密和解密。 一、问题 自己想创建一个user表&#xff0c;user表中有一个password属性列&#xff0c;自己想对密码进行加密后再存入数据库&#xff0c…

java质数的判断 C语言指针变量的使用

1. public static void main(String[] args) {Scanner scnew Scanner(System.in);System.out.println("请输入一个值");int num sc.nextInt();boolean flagtrue;for (int i2;i<num;i){if (num%i0){flagfalse;break;}}if (flag){System.out.println(num"是一…

Midjourney 3D:探索未来沉浸式体验的无限可能

一、Midjourney 3D:开启沉浸式新时代 最近,Midjourney宣布即将推出一款全新的3D产品,这不仅仅是一次简单的3D生成技术的升级,而是一场革命。这款新产品将基于先进的光场技术,而非传统的3D网格模型,为用户提供前所未有的沉浸式体验。用户不仅可以“跳入”生成的场景中自由…

CasPL: Cascade Prompt Learning for Vision-Language Model Adaptation

文章汇总 当前的问题 目前可学习的提示符号主要用于适应任务的单一阶段(即适应提示)&#xff0c;容易导致过度拟合风险。 动机 提示符将分两个阶段逐步优化。在初始阶段&#xff0c;学习增强提示&#xff0c;**通过使用大量未标记的领域图像数据对齐其预测逻辑&#xff0c;从…

【文献及模型、制图分享】基于投入品减量增效视角的长江经济带农业生产绿色化演进研究

文献介绍 绿色化转型是农业可持续发展研究的重要议题。以农业生产绿色化转型过程的理论分析为基础&#xff0c;运用文献调查、访谈与问卷调查、脱钩分析相结合的方法&#xff0c;研究了长江经济带农业生产绿色化转型过程和投入品减量增效的趋势。 结果表明&#xff1a; 2015…

记录一个容器间访问不通问题

docker-compose装了zookeeper和一个服务。 zk服务如下&#xff1a; szxc-zk:image: "image.sd001.cn:30003/base/zookeeper:3.8"privileged: trueenvironment:- "TZAsia/Shanghai"#- "ALLOW_ANONYMOUS_LOGINyes"- "ZOO_MY_ID1"- &qu…

redis详细教程(3.ZSet,Bitmap,HyperLogLog)

ZSet Redis 的 ZSet&#xff08;有序集合&#xff09;是一种特殊的数据类型&#xff0c;它允许存储一系列不重复的字符串元素&#xff0c;并为每个元素关联一个分数&#xff08;score&#xff09;。这个分数用于对集合中的元素进行排序。ZSet 的特点是&#xff1a; 唯一性&am…

【Windows】电脑端口明明没有进程占用但显示端口被占用(动态端口)

TOC 一、问题 重启电脑后&#xff0c;启用某个服务显示1089端口被占用。 查看是哪个进程占用了&#xff1a; netstat -aon | findstr "1089"没有输出&#xff0c;但是换其他端口&#xff0c;是可以看到相关进程的&#xff1a; 现在最简单的方式是给我的服务指定另…

RHCE的学习(8)

动态网站 lnmp&#xff08;LAMP&#xff09; 解析index.php界面 &#xff08;1&#xff09;预配&#xff0c;确保服务能够被访问 systemctl stop firewalld setenforce 0 &#xff08;2&#xff09;安装nginx服务 mount /dev/sr0 /mnt cat /etc/yum.repos.d/base.repo dnf …

【待学习 】 DHTMLX Gantt

DHTMLX Gantt是一个开源 JavaScript 甘特图库&#xff0c;可以帮助您以美观的图表形式说明和管理项目计划。 它可以将任务之间的依赖关系显示为线条&#xff0c;并允许您设置任务之间的不同关系&#xff08;完成-开始、开始-开始、完成-完成、开始-完成&#xff09;。标准版还…

一二三应用开发平台自定义查询设计与实现系列2——查询方案功能实现

查询方案功能实现 上面实现了自定义查询功能框架&#xff0c;从用户角度出发&#xff0c;有些条件组合可以形成特定的查询方案&#xff0c;对应着业务查询场景。诸多查询条件的组合&#xff0c;不能每次都让用户来设置&#xff0c;而是应该保存下来&#xff0c;下次可以直接使…

一文解决单调栈的应用

单调栈的定义&#xff1a; 单调栈是栈的一中特殊形式&#xff0c;在栈中的元素必须满足单调性&#xff08;一定是单调上升或单调下降等等的规律&#xff09;。 单调栈的性质&#xff1a; 单调栈解决的问题 单调栈解决的常见问题&#xff1a;给定一个序列&#xff0c;求每个位置…

css绘制s型(grid)

在之前有通过flex布局实现了s型布局&#xff0c;是通过截取数组形式循环加载数据 这次使用grid直接加载数据通过css实现 <div id"app"><template v-for"(item,inx) in items"><div class"row"><template v-for"(ite…

SpringBoot 集成RabbitMQ 实现钉钉日报定时发送功能

文章目录 一、RabbitMq 下载安装二、开发步骤&#xff1a;1.MAVEN 配置2. RabbitMqConfig 配置3. RabbitMqUtil 工具类4. DailyDelaySendConsumer 消费者监听5. 测试延迟发送 一、RabbitMq 下载安装 官网&#xff1a;https://www.rabbitmq.com/docs 二、开发步骤&#xff1a;…

微信小程序美团点餐

引言&#xff1a;外卖已经成为了都市人的必备&#xff0c;在无数个来不及&#xff08;懒得&#xff09;做饭的时刻拯救孤单寂寞的胃。美团外卖无疑是外卖届的领头羊&#xff0c;它的很多功能与设计都值得我们学习。本文将从五个方面&#xff0c;对美团外卖展开产品分析&#xf…

vue封装信号强度

图标下载链接: https://pan.baidu.com/s/1828AidkCKU1KTkw1SvBwQg?pwd4k7n 共五格信号 信号5为绿色&#xff0c;信号4为绿色&#xff0c;信号3为黄色&#xff0c;信号2为黄色&#xff0c;信号1为红色&#xff0c;信号0为灰色。 子组件 /components/SignalStrength/index.vu…

【Python爬虫实战】深入解析 Selenium:从元素定位到节点交互的完整自动化指南

#1024程序员节&#xff5c;征文# &#x1f308;个人主页&#xff1a;易辰君-CSDN博客 &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html ​ 前言 Selenium 是进行网页自动化操作的强大工具&#xff0c;在测试、数据抓取、用户行…

Sqoop的安装配置及使用

Sqoop安装前需要检查之前是否安装了Tez,否则会产生版本或依赖冲突&#xff0c;我们需要移除tez-site.xml&#xff0c;并将hadoop中的mapred-site.xml配置文件中的mapreduce驱动改回成yarn&#xff0c;然后分发到其他节点&#xff0c;hive里面配置的tez也要移除&#xff0c;然后…

实战应用WPS WebOffice开放平台服务

概述 根据公司的业务需要&#xff0c;主要功能是在线编辑文档&#xff0c;前端的小伙伴进行的技术调研&#xff0c;接入的是WPS WebOffice&#xff0c;这里只阐述技术介入的步骤、流程和遇到的坑进行的一些总结。 实践 WPS WebOffice 开放平台进行认证 在开始之前&#xff…