Java高级特性 - 多线程基础(1)使用线程第1关:创建线程第2关:使用 Callable 和 Future 创建线程

news/2024/5/17 19:30:05/文章来源:https://blog.csdn.net/weixin_54570972/article/details/129813150

目录

第1关:创建线程

头歌知识点总结: 

第2关:使用 Callable 和 Future 创建线程

本题头歌知识点 

本题详解:

第1关:创建线程

package step1;
//请在此添加实现代码
//使用继承Thread类的方式创建一个名为 ThreadClassOne 的类,重写的run方法需要实现输出0-10之间的奇数,输出结果如下:1 3 5 7 9;
/********** Begin **********/
public class ThreadClassOne extends Thread { //创建一个类来继承Thread类public void run(){ //重写父类的run 方法for(int i=0;i<=10;i++){if(i%2 == 1)System.out.print(i+" ");}}
}//使用实现Runnable接口的方式创建一个名为ThreadClassTwo的类,重写run方法,编写start方法,run方法需要实现打印0-10之间的偶数,输出结果如下:0 2 4 6 8 10
class ThreadClassTwo implements Runnable{public void run(){for(int i=0;i<=10;i++){if(i%2==0) System.out.print(i+" ");}}
}
/********** End **********/

头歌知识点总结: 

 

相关知识

不知道你有没有发现,截止目前,我们编写的代码都是在main()函数中依照编写代码的顺序从上到下依次运行的。

但是我们平常使用的软件基本都是可以多个任务同时执行的,这其中的运行机制是什么呢?这一小节我们就来探讨。

本小节我们来学习Java中程序是如何同时执行多个任务的。

为了完成本关任务,你需要掌握:

1.什么是线程、什么是进程;

2.如何创建线程。

什么是线程、什么是进程

Java中要同时执行(如果是单核,准确的说是交替执行)多个任务,使用的是多线程,而要理解线程,我们先要了解什么是进程什么是线程。

一般的定义:进程是指在操作系统中正在运行的一个应用程序,线程是指进程内独立执行某个任务的一个单元。

怎么理解呢?

比如说QQ是是一个进程,如果你在和A朋友语音聊天的同时和B朋友打字聊天,同时还在QQ群下载图片,这三个操作就相当于开启了三个线程,可以说有了线程之后我们设计的程序就可以一边执行A操作,一边执行B操作了。

线程和进程有什么区别呢?首先最直观的就是:一个进程可拥有多个线程。 具体比较:

  • **调度 ** 进程拥有资源; 线程是调度和分派的基本单位; 同一进程中线程的切换不会引起进程的切换; 进程间的线程切换则会引起进程切换从而导致资源切换等。

  • **并发性 ** 进程:进程和进程之间可并发执行 ; 线程:除了进程间的并发执行还可以线程之间并发执行; 线程的并发性更高。

  • **拥有资源 ** 线程并不能拥有资源,只有进程才拥有资源。

  • **系统开销 ** 进程创建、切换和撤销都会导致系统为之创建或者回收进程控制卡以及资源,但是线程的创建以及线程间的切换并不会引起系统做这些事儿,所以线程的系统开销明显更小。

如何创建线程

在这里我们主要掌握两种创建线程的方式。

1.继承Thread类;

我们可以使用继承Thread类的方式来创建一个线程。 创建一个类来继承Thread类,重写父类的run方法,就实现了创建我们自己的线程了。之后调用线程的start方法,就算是开启了一个线程了。

示例:

class MyThread extends Thread{
private String name;public MyThread(String name) {
super();
this.name = name;
}
public void run() {
System.out.println("线程" + name +"开始运行");for (int i = 0; i < 5; i++) {
System.out.println("线程" + name + "运行" + i);
}
System.out.println("线程" + name + "结束");
}
}public class Test {
public static void main(String[] args) {
Thread t = new MyThread("T!");
t.start();
Thread t2 = new MyThread("T2");t2.start();}
}

运行结果: 线程T!开始运行 线程T2开始运行 线程T!运行0 线程T2运行0 线程T!运行1 线程T2运行1 线程T!运行2 线程T!运行3 线程T!运行4 线程T2运行2 线程T2运行3 线程T2运行4 线程T2结束 线程T!结束

运行这段代码我们会发现,线程是交替运行的,并且每次运行输出的结果都不一样,输出是随机的。

2.实现Runnable接口。

最简单创建线程的方法就是实现一个Runnable接口了,实际上所有的线程都是直接或者间接实现了Runnable接口的,上一个例子中Thread类其实就实现了Runnable接口。

示例:

class MyThread implements Runnable {
private String name;
private Thread mythread;public MyThread(String name) {
super();
this.name = name;
}public void run() {for (int i = 0; i < 5; i++) {
System.out.println("线程" + name + "运行" + i);
}
System.out.println("线程" + name + "结束");
}public void start() {
System.out.println("线程开始: " + name);
if (mythread == null) {
mythread = new Thread(this, name);
mythread.start();
}
}}public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread("T1");
t1.start();
MyThread t2 = new MyThread("T2");
t2.start();
}
}

运行结果:

线程开始: T1 线程开始: T2 线程T1运行0 线程T2运行0 线程T1运行1 线程T1运行2 线程T1运行3 线程T1运行4 线程T1结束 线程T2运行1 线程T2运行2 线程T2运行3 线程T2运行4 线程T2结束

Java1.5版本之后,还提供了一种创建线程的方式: 通过Callable 和 Future 创建线程,这个我们将在之后的实训中学习到。

创建线程的两种方式对比

  • 实现Runnable创建线程时,线程类只是实现了Runnable接口,还可以继承其他的类。

  • 继承THread类创建线程时,线程类继承了Thread类,不能再继承其他类。不过这种方式编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用this即可获得当前线程。

java程序默认启动的线程

Java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候,实际上都会启动一个jvm,每一个jvm实际在就是在操作系统中启动了一个进程。

编程要求

请仔细阅读右侧代码,根据方法内的提示,在Begin - End区域内进行代码补充,具体任务如下:

  • 使用继承Thread类的方式创建一个名为 ThreadClassOne 的类,重写的run方法需要实现输出0-10之间的奇数,输出结果如下: 1 3 5 7 9

  • 使用实现Runnable接口的方式创建一个名为ThreadClassTwo的类,重写run方法,编写start方法,run方法需要实现打印0-10之间的偶数,输出结果如下: 0 2 4 6 8 10

    public ThreadClassOne (){super();}public ThreadClassTwo(){super();}
    这是一个Java类的构造函数。
    它是一个无参构造函数,因为它没有参数。
    它的作用是调用父类的构造函数,即Thread类的构造函数。
    在Java中,如果没有明确指定调用父类构造函数,则会自动调用父类的无参构造函数。
    因此,这个构造函数可以省略,因为它的作用和默认的无参构造函数是一样的。

第2关:使用 Callable 和 Future 创建线程

package step2; //声明该类所在的包import java.util.concurrent.Callable; //引入需要使用的类
import java.util.concurrent.FutureTask;public class Task { //定义task类public void runThread(int num) { //定义runThread方法//请在此添加实现代码
/********** Begin **********/
// 在这里开启线程 获取线程执行的结果
/*这三句代码的作用是创建一个可在另一个线程中执行的任务,并将其封装在一个FutureTask对象中,最后将该FutureTask对象传递给一个新的线程对象,以便在该线程中执行这个任务*/
ThreadCallable t1 = new ThreadCallable(num); 
FutureTask<Integer> ft1 = new FutureTask<>(t1); 
Thread thread1 = new Thread(ft1,"thread1"); thread1.start(); // 启动线程
try{ // 获取线程执行的结果System.out.println("线程的返回值为:"+ft1.get());
}catch(Exception e){e.printStackTrace();
}
/********** End **********/}
}//请在此添加实现代码
/********** Begin **********/
/* 在这里实现Callable接口及方法 */
class ThreadCallable implements Callable<Integer>    {int num;ThreadCallable(int num){this.num = num;}ThreadCallable(){}public Integer call() throws Exception{return getNum(num);}private int getNum(int num){if(num<3){return 1;}else{return getNum(num-1) +getNum(num-2);}}
}/********** End **********/

本题头歌知识点 

Java1.5版本开始,就提供了 CallableFuture 来创建线程,这种方式也是在Java程序员面试中经常会被问到的问题。

上一小节介绍了ThreadRunnable两种方式创建线程,不过这两种方式创建线程都有一个缺陷:在执行完任务之后无法获取执行结果。 如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。

而如果使用CallableFuture,通过它们就可以在任务执行完毕之后得到任务执行结果

本小节你需要掌握的知识有:

1.什么是CallableFuture

2.如何通过CallableFuture创建线程。

Callable和Future

它们俩其实挺有意思,在运行的时候各司其职,Callable产生结果Future获取结果

使用步骤如下:

  1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值;

  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值;

  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程;

  4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

接下来通过一个示例来学习这两个对象的使用:

public class Test {
public static void main(String[] args) {
CallableThreadTest cts = new CallableThreadTest();
// 接收
FutureTask<Integer> ft = new FutureTask<>(cts);new Thread(ft, "有返回值的线程").start();
for (int i = 0; i < 30; i++) {
System.out.println( "main" + " 的循环变量i的值:" + i);
}try {
System.out.println("子线程的返回值:" + ft.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}class CallableThreadTest implements Callable<Integer> {public Integer call() throws Exception {
int i = 0;
for (; i < 30; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}

运行这段程序你应该可以获取到类似如下结果(每次运行的结果不一致): ... ... main 的循环变量i的值:28 main 的循环变量i的值:29 有返回值的线程 23 有返回值的线程 24 有返回值的线程 25 有返回值的线程 26 有返回值的线程 27 有返回值的线程 28 有返回值的线程 29 子线程的返回值:30

由于输出过长,省略了部分结果,可以发现在最后接收到了子线程的返回值。

在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!

细心的你会发现这个结果是call函数的返回值,怎么拿到这个返回值的呢?是通过FutureTask拿到的,使用ft.get()方法即可获得线程的返回值,这就是一个简单的使用Callable和Future的过程了。

关于Callable和Future的使用,以及他们的常用函数,我们将会在后续的实训中学习。

本题详解:

(一)

ThreadCallable t1=new ThreadCallable(num);
FutureTask<Integer> ft1=new FutureTask<>(t1);
Thread thread1=new Thread(ft1,"thread1");

这三句代码的作用是创建一个可以在另一个线程中执行的可调用对象,并将其封装在一个FutureTask对象中,最后将该FutureTask对象传递给一个新的线程对象,以便在该线程中执行这个可调用对象。

具体来说:

  1. 第一行代码创建了一个ThreadCallable对象t1,它实现了Callable接口,该接口表示一个可调用的任务,可以在另一个线程中执行,并返回一个结果。

  2. 第二行代码创建了一个FutureTask对象ft1,它是一个可调用的任务,它封装了t1对象,可以在另一个线程中执行,并返回一个结果。FutureTask是一种特殊的RunnableFuture,它表示一个可以取消的异步计算任务,它可以执行Callable或Runnable任务,并保存计算结果。

  3. 第三行代码创建了一个Thread对象thread1,它接收一个FutureTask对象ft1作为参数,并将其封装新的线程中执行,线程的名称是“thread1”。

综上所述,这三句代码的作用是创建一个可在另一个线程中执行的任务,并将其封装在一个FutureTask对象中,最后将该FutureTask对象传递给一个新的线程对象,以便在该线程中执行这个任务。

(二)

try{System.out.println("线程的返回值为:"+ft1.get());
}catch(Exception e){e.printStackTrace();
}

这段代码的作用是获取线程执行的结果,并将结果打印出来。

具体来说:

  1. 第一行代码调用FutureTask对象ft1的get()方法获取线程的返回值。get()方法是一个阻塞方法,如果线程还没有执行完毕,它会一直阻塞直到线程执行完毕并返回结果

  2. 如果线程执行成功,get()方法会返回线程的返回值,这个返回值的类型是Integer。

  3. 第二行代码将线程的返回值打印出来,以便查看执行结果。

  4. 如果线程执行过程中出现了异常,get()方法会抛出一个异常,这个异常需要在catch块中进行处理。通常情况下,我们会打印异常的堆栈信息,以便查看异常的原因和位置。

因此,这段代码的作用是获取线程执行的结果,并将结果打印出来,同时处理可能出现的异常情况。

(三)

这段代码定义了一个类ThreadCallable,它实现了Callable接口,并指定了泛型参数为Integer,表示线程执行的结果是一个整数。

具体来说:

  1. 类中定义了一个成员变量num,表示要计算斐波那契数列的第几项。

  2. 类中定义了一个构造方法ThreadCallable(int num),用于初始化成员变量num。

  3. 类中定义了另一个构造方法ThreadCallable(),这个构造方法没有参数,什么也不做,可能是为了方便创建对象而定义的。

  4. 类中实现了call()方法,这个方法是Callable接口中的一个方法,表示线程需要执行的任务。在这个方法中,调用了getNum(num)方法计算斐波那契数列的第num项,并将结果返回。

  5. 类中定义了一个私有方法getNum(int num),这个方法用递归的方式计算斐波那契数列的第num项。当num小于3时,返回1;否则,返回getNum(num-1) +getNum(num-2)的结果。

因此,这段代码的作用是定义了一个线程任务,用于计算斐波那契数列的第num项,并将结果作为线程的返回值。这个任务使用递归的方式实现,当num小于3时,返回1;否则,返回前两项的和。

 

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

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

相关文章

【C++项目】高并发内存池

前言&#xff1a; 本篇博客大致记录基于tcmalloc实现高并发内存池的思想与实现方案。 使用语言&#xff1a;C&#xff0c;编译器&#xff1a;vs2022&#xff0c;开始时间&#xff1a;2023/4/3&#xff0c;结束时间&#xff1a;2023/4/12。 项目源码地址&#xff1a;Cproject: 我…

苹果智能戒指专利曝光,Find My技术加持不易丢

根据美国商标和专利局&#xff08;USPTO&#xff09;公示的清单&#xff0c;苹果近日获得了一项“智能戒指”相关的设计专利&#xff0c;编号为“US 11625098 B2”。 这款智能戒指专利主要服务于增强现实&#xff08;AR&#xff09;或者虚拟现实&#xff08;VR&#xff09;场…

C语言CRC-16 MAXIM格式校验函数

C语言CRC-16 MAXIM格式校验函数 CRC-16校验产生2个字节长度的数据校验码&#xff0c;通过计算得到的校验码和获得的校验码比较&#xff0c;用于验证获得的数据的正确性。基本的CRC-16校验算法实现&#xff0c;参考&#xff1a; C语言标准CRC-16校验函数。 不同厂家通过对输入…

基于ESP32和blinker的红外小夜灯控制

一. 系统设计及框图&#xff1a; 本设计可以实现通过手机APP使用蓝牙或WIFI远程控制红外设备&#xff0c;也可以通过离线语音模块语音控制红外设备。可以控制市面上常见的NEC格式的红外设备, 这里是控制小夜灯&#xff0c;其它红外设备在控制原理上是相通的。本设计可用作课程…

如何免费使用ChatGPT 4?

自从ChatGPT发布以来&#xff0c;它就取得了巨大的成功。无论是常春藤法学考试还是商学院作业&#xff0c;ChatGPT都被用于各种试验。统计数据显示&#xff0c;ChatGPT每月吸引约9600万用户。随着ChatGPT的巨大成功&#xff0c;Open AI最近推出了它的最新版本&#xff0c;名为“…

Docker本地推送到hub,以及上传时遇到的问题解决

1.在本地创建一个 Dockerfile FROM ubuntu:latest RUN apt-get update && apt-get install -y curl CMD ["curl", "https://www.baidu.com"]2.在本地构建 Docker 镜像 在创建本地docker镜像的时候[TAG] .和[TAG] /PATH/TO 需要注意dockerfile文件…

Rust China Conf 2023 筹备启动:议题征集开始

大会介绍Rust China Conf 2023 由 Rust 中文社区发起主办、知名企业和开源组织联合协办&#xff0c;是年度国内规模最大并唯一的 Rust 线下大型会议&#xff0c;深受 Rust 中文社区开发者与相关企业的喜爱与推崇。本次大会为线下会议&#xff0c;将于6月17日-18日在上海举办&am…

企业推广常用的网络推广方法有哪些?

网络推广是指通过互联网向目标用户推广产品、服务或品牌的过程&#xff0c;其主要目的是为了扩大业务范围&#xff0c;提高企业知名度&#xff0c;增加销售额。在当今的数字化时代&#xff0c;网络推广已经成为了企业不可或缺的一部分。本文将介绍一些常见的网络推广方法和途径…

yolov5详解与改进

https://github.com/z1069614715/objectdetection_script YOLOV5改进-Optimal Transport Assignment Optimal Transport Assignment&#xff08;OTA&#xff09;是YOLOv5中的一个改进&#xff0c;它是一种更优的目标检测框架&#xff0c;可以在保证检测精度的同时&#xff0c…

一份两年前一个月的工作经历没写在简历上,背调前主动坦白,却被背调公司亮了红灯,到手的offer没了!...

只因为简历上漏写了一份一个月的工作&#xff0c;就被亮了背调红灯&#xff0c;这公平吗&#xff1f;一位网友就被狠狠坑了一把&#xff0c;来看下他的遭遇&#xff1a;他有一份两年前、时长一个月的工作经历没写在简历上&#xff0c;背调前主动和背调公司还有招聘方hr都说了这…

【Linux】浅析Input子系统

文章目录1. 框架1.1 数据结构1.2 evdev_handler1.3 evdev_init1.4 input_register_handler2. 应用如何打开节点并读取到事件数据2.1 evdev_fops2.2 evdev_open2.3 evdev_release2.4 evdev_read2.5 evdev_write2.6 evdev_poll2.7 evdev_fasync2.8 evdev_ioctl2.9 evdev_ioctl_co…

opcua 获取自定义结构体的成员值

示例中的节点值的数据类型为自定义的多层嵌套的结构体如下: 获取改结构体的成员值: import opcua from opcua import ua from opcua.ua import uatypesdef user_defined_vars(value_dict, # 自定义数据类型的变量值name_prefix, # 成员名前缀比如aa.bbdata_dict2, # 用…

API 接口设计

1、场景描述 比如说我们要做一款 APP&#xff0c;需要通过 api 接口给 app 提供数据。假设我们是做商城&#xff0c;比如我们卖书的。我们可以想象下这个 APP 大概有哪些内容&#xff1a; 1&#xff09;首页&#xff1a;banner 区域&#xff08;可以是一些热门书籍的图片做推广…

第十四届蓝桥杯大赛软件赛省赛 C/C++ 大学 A 组题解+个人总结

提示&#xff1a;此题解为本人自己解决&#xff0c;如有差错请大家多多指正。 文章目录题解总结一、幸运数1.试题2.解法3.代码二、[有奖问答](https://blog.csdn.net/A2105153335/article/details/130038980?spm1001.2014.3001.5501)三、[平方差](https://blog.csdn.net/A2105…

js flyout 2: VScroll

目录版权描述测试页面showFlyout问题1 - scroll 实现可能不准?问题2 - 容器内容重排可导致浮层错位关于重排小结附录 - 完整代码版权 本文为原创, 遵循 CC 4.0 BY-SA 版权协议, 转载需注明出处: https://blog.csdn.net/big_cheng/article/details/130101031. 文中代码属于 pu…

数据结构与算法01 稀疏数组

稀疏数组问题 当一个二维数组中大部分数据都是0&#xff0c;对这个数组直接进行存储会很浪费空间&#xff0c;因此利用稀疏数组进行压缩&#xff0c;稀疏数组第一行的第一个元素是原二维数组行数。&#xff0c;第一行的第二个元素是原二维数组的列数&#xff0c;如图为11行11列…

6.S081——虚拟内存部分——xv6源码完全解析系列(4)

0.briefly speaking 点击跳转到上一篇博客 好&#xff0c;现在进入下一个话题&#xff0c;就是物理内存分配器(kernel/kalloc.c)。在简单介绍完内核态的物理内存分配器之后&#xff0c;之后简单带过一下两个头文件riscv.h和memorylayout.h这两个头文件&#xff0c;因为它们都…

2.5d风格的游戏模式如何制作

文章目录一、 介绍二、 绘制瓦片地图三、 添加场景物体&#xff0c;添加碰撞器四、 创建玩家五、 创建玩家动画六、 玩家脚本七、 2d转换成2.5d八、 “Q”键向左转动视角、“E”键向右转动视角九、 下载工程文件一、 介绍 制作一个类似饥荒风格的2.5d游戏模板。 2.5D游戏是指以…

表id自增的方法

数据库主键id自增的方法&#xff0c;列举了几种如下 一、数据库自增&#xff08;部分数据库支持&#xff09; 创建表的时候设置id自增即可&#xff0c;或者后期修改表id自增 # mysql 语法 create table your_table_name(id bigint(20) not null auto_increment primary key …

Markdown 语法大全

Markdown是一种轻量级标记语言&#xff0c;常用于撰写博客、文档、论文等。它可以让你使用易读易写的纯文本格式来编写文档&#xff0c;然后通过转换成有效的HTML文档进行发布。以下是Markdown常用的语法&#xff1a; 这里写目录标题标题列表引用一级引用嵌套引用粗体和斜体删除…