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

news2024/11/29 22:49:43

目录

一、多线程的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 {
    @Override
    public 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 {
    @Override
    public 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> {
    @Override
    public 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()方法
    @Override
    public 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.coloradmin.cn/o/2227076.html

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

相关文章

Linux补基础之:网络配置

目录 一、检查主机与虚拟机是否能正常通信 二、网络的连接模式 桥接模式 流程 特点 NAT模式 流程 特点 仅主机 流程 特点 三、修改静态IP 四、可能遇到的问题 防火墙 DNS 五、主机名更改 六、登录服务器 实际的大数据管理中&#xff0c;会有由很多服务器构成的…

Android 原生开发与Harmony原生开发浅析

Android系统 基于Linux ,架构如下 底层 (Linux )> Native ( C层) > FrameWork层 (SystemService) > 系统应用 (闹钟/日历等) 从Android发版1.0开始到现在15,经历了大大小小的变革 从Android6.0以下是个分水岭,6.0之前权限都是直接卸载Manifest中配置 6.0开始 则分普…

初识WebGL

思路&#xff1a; 构建<canvas>画布节点&#xff0c;获取其的实例。使用getWebGLContext() 拿到画布上下文。拿到上下文用clearColor() 设置背景颜色。最后清空canvas画布,是为了清除颜色缓冲区。 html结构&#xff1a; <!DOCTYPE html> <html lang"en&…

w外链如何跳转微信小程序

要创建外链跳转微信小程序&#xff0c;主要有以下几种方法&#xff1a; 使用第三方工具生成跳转链接&#xff1a; 注册并登录第三方外链平台&#xff1a;例如 “W外链” 等工具。前往该平台的官方网站&#xff0c;使用手机号、邮箱等方式进行注册并登录账号。选择创建小程序外…

【华为HCIP实战课程二十一】OSPF区域间汇总配置详解,网络工程师

一、OSPF汇总和默认路由 1、大规模的OSPF网络配置路由汇总,减小路由表的规模。 2、路由汇总讲多条连续的IP前缀汇总成一条路由前缀。 3、可以避免网络中的路由震荡,提高网络的稳定性。 4、ABR/ASBR完成路由汇总。 [R4-ospf-1-area-0.0.0.0]abr-summary 11.1.0.0 255.255.…

在C#中使用指针

C#向开发人员隐藏了大部分基本内存管理操作&#xff0c;因为它使用了垃圾回收器和引用。但是&#xff0c;有时候我们也需要直接访问内存&#xff0c;例如&#xff1a;进行平台调用&#xff0c;性能优化等等。 .Net平台定义了两种主要数据类型&#xff1a;值类型和引用类型&…

前端零基础入门到上班:【Day2】开发环境VSCode安装

VSCode 安装教程&#xff1a;图文保姆教程 引言 在前端开发中&#xff0c;选择合适的代码编辑器是提高工作效率的重要一步。Visual Studio Code&#xff08;简称 VSCode&#xff09;作为一款强大的开源编辑器&#xff0c;因其简洁易用、功能强大、扩展性好而广受开发者喜爱。…

MES系列- 统计过程分析(SPC)实现

MES系列文章目录 ISA-95制造业中企业和控制系统的集成的国际标准-(1) ISA-95制造业中企业和控制系统的集成的国际标准-(2) ISA-95制造业中企业和控制系统的集成的国际标准-(3) ISA-95制造业中企业和控制系统的集成的国际标准-(4) ISA-95制造业中企业和控制系统的集成的国际标准…

面对复杂的软件需求:5大关键策略!

面对软件需求来源和场景的复杂性&#xff0c;有效地管理和处理需求资料是确保项目成功的关键&#xff0c;能够提高需求理解的准确性&#xff0c;增强团队协作和沟通&#xff0c;降低项目风险&#xff0c;提高开发效率。反之&#xff0c;项目可能面临需求理解不准确、团队沟通不…

react 基础学习笔记

1.react 语法 ①数据渲染 函数组件将HTML结构直接写在函数的返回值中 JSX只能有一个根元素 JSX插值写法 插值可以使用的位置 1.标签内容&#xff1b; 2.标签属性 JSX 条件渲染&#xff1a;三目运算符&#xff1b; JSX根据数据进行列表渲染&#xff1a;map()方法&#x…

Elastic Stack - FileBeat 入门浅体验

Filebeat 是 Elastic Stack 中的一个轻量级日志转发器&#xff0c;主要用于收集和转发日志数据。Filebeat 作为代理安装在您的服务器上&#xff0c;可以监控您指定的日志文件或位置&#xff0c;收集日志事件&#xff0c;并将其转发到 Elasticsearch 或 Logstash 进行索引。 一…

XCode16中c++头文件找不到解决办法

XCode16中新建Framework&#xff0c;写完自己的c代码后&#xff0c;提示“<string> file not found”等诸如此类找不到c头文件的错误。 工程结构如下&#xff1a; App是测试应用&#xff0c;BoostMath是Framework。基本结构可以参考官方demo&#xff1a;Mix Swift and …

“循环购体系:创新消费回馈模式引领电商新风尚“

各位听众&#xff0c;你们好&#xff0c;我是吴军&#xff0c;今天我想与你们分享一种创新且引人注目的商业模式——循环购体系。这是一种融合了消费回馈与积分制度的新型购物模式&#xff0c;它在顾客与商家之间搭建了一个全新的、互动性强的桥梁。 在循环购体系的运作中&…

云联网对等连接--实现内网互通

云联网 今天给大家介绍一款产品&#xff0c;腾讯云的云联网。 云联网&#xff1a;为您提供云上私有网络间&#xff08;VPC&#xff09;、VPC 与本地数据中心间&#xff08;IDC&#xff09;内网互联的服务&#xff0c;具备全网多点互联、路由自学习、链路选优及故障快速收敛等…

详细解读 CVPR2024:VideoBooth: Diffusion-based Video Generation with Image Prompts

Diffusion Models专栏文章汇总:入门与实战 前言:今天是程序员节,先祝大家节日快乐!文本驱动的视频生成正在迅速取得进展。然而,仅仅使用文本提示并不足以准确反映用户意图,特别是对于定制内容的创建。个性化图片领域已经非常成功了,但是在视频个性化领域才刚刚起步,这篇…

构建自然灾害预警决策一体化平台,筑牢工程安全数字防线

近年来&#xff0c;国家和部委也强调了要切实加强地质灾害监测预警。作为国内智慧应急领域的先行者&#xff0c;Mapmost持续探索利用数字孪生技术&#xff0c;推进自然灾害风险预警精细化&#xff0c;强化对监测数据的综合分析和异常信息研判处置。建立健全区域风险预警与隐患点…

.NET Core WebApi第7讲:项目的发布与部署

一、理解 二、项目的发布与部署 1、点击Publish进行发布 2、等待生成publish文件&#xff0c;如下图 3、把上图中发布的文件在服务器里面装上&#xff0c;即在windows的IIS里把它挂上去。如此便可以直接去访问当前的前/后端了。 &#xff08;1&#xff09; 注意&#xff1a;…

Python自动化测试中的Mock与单元测试实战

在软件开发过程中&#xff0c;自动化测试是确保代码质量和稳定性的关键一环。而Python作为一门灵活且强大的编程语言&#xff0c;提供了丰富的工具和库来支持自动化测试。本文将深入探讨如何结合Mock与单元测试&#xff0c;利用Python进行自动化测试&#xff0c;以提高代码的可…

前端获取csv或者excel 静态数据并使用

这里我将空格全部替换成了 || 好让我变成数组&#xff0c;从而拿到每一条数据中的第一项&#xff0c;相当于excel或者csv文件的第一列的东西 axios.get("/csv/zhongxiang").then((res) > {let rows res.data.split("\n");for (let row of rows) {let c…

Axios 请求超时设置无效的问题及解决方案

文章目录 Axios 请求超时设置无效的问题及解决方案1. 引言2. 理解 Axios 的超时机制2.1 Axios 超时的工作原理2.2 超时错误的处理 3. Axios 请求超时设置无效的常见原因3.1 配置错误或遗漏3.2 超时发生在建立连接之前3.3 使用了不支持的传输协议3.4 代理服务器或中间件干扰3.5 …