Java多线程--JDK5.0新增线程创建方式

文章目录

  • 一、新增方式1:实现Callable接口
    • (1)介绍
    • (2)案例
    • (3)总结对比
  • 二、新增方式2:使用线程池
    • (1)问题与解决思路
      • 1、现有问题
      • 2、解决思路
      • 3、好处
    • (2)线程池相关API
    • (3)案例

一、新增方式1:实现Callable接口

(1)介绍

【Callable接口】

@FunctionalInterface
public interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}

<>是泛型的意思,V决定了call()方法返回值的类型。

  • 与使用Runnable相比, Callable功能更强大些
    • 相比run()方法,可以有返回值。
    • 方法可以抛出异常。
    • 支持泛型的返回值(需要借助FutureTask类,获取返回结果)。
  • Future接口(了解)
    • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
    • FutureTask是Futrue接口的唯一的实现类。
    • FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
  • 缺点:在获取分线程执行结果的时候,当前线程(或是主线程)受阻塞,效率较低。

(2)案例

【步骤】

1、创建一个实现Callable的实现类。

2、实现call方法,将此线程需要执行的操作声明在call()中。

3、创建Callable接口实现类的对象。

4、将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象。

5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()

6、获取Callablecall方法的返回值。

【案例】

🌱代码

package yuyi04.callable;import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;/*** ClassName: Callable* Package: yuyi04.callable* Description:*  创建多线程的方式三:实现Callable (jdk5.0新增的)* @Author 雨翼轻尘* @Create 2024/2/3 0003 13:59*///1.创建一个实现Callable的实现类
class NumThread implements Callable {//2.实现call方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {	//遍历100以内的偶数if (i % 2 == 0) {System.out.println(i);sum += i;	//计算100以内偶数的和}Thread.sleep(1000);}return sum;}
}public class CallableTest {public static void main(String[] args) {//3.创建Callable接口实现类的对象NumThread numThread = new NumThread();//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象FutureTask futureTask = new FutureTask(numThread);//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()Thread t1 = new Thread(futureTask);t1.start();System.out.println("main()线程");try {//6.获取Callable中call方法的返回值//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。Object sum = futureTask.get();System.out.println("总和为:" + sum);} catch (Exception e) {e.printStackTrace();}}}

🍺输出(部分)

image.png

☕注意

call方法就相当于之前的run方法,与run方法不一样的是,call方法可以有返回值类型

另外,call方法可以通过throws抛出异常。也就是异常可以不在里面处理了,可以往外抛,更加灵活了。

之前在run方法里面,sleep方法有一个异常,可能会出现编译时异常InterruptedException,所以就会在同步代码块里面做try-catch操作。如下:

class PrintNumber implements Runnable{//...@Overridepublic void run() {while(true){synchronized (obj) {   //obj是唯一的,线程安全//...if(number<=100){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//...}else{break;}}}}
}

如果就想在外面知道方法调用可能出现了什么异常,也可以在这里throw一个运行时异常,将编译时异常InterruptedException以字符串的方式传进去,如下:

image.png

这就是一种处理方式,在外面想要知道出现了什么异常,也能抛,只不过此时抛的是一个运行时异常,运行时异常通常不处理。

就避开了在sleep的地方直接throws出去。

相当于将可能出现的编译时异常以一个运行时异常的方式给抛出

而现在的Callable比较灵活,可以直接将异常抛出去。

而且它抛的还是Exception,也就是说,无论是编译时异常还是运行时异常都可以。如下:

image.png

call方法可以返回值,而run方法不行,比如上边案例中,run方法就只能将sum定义在外面,在里面给sum赋值,方法执行结束以后,sum的值也就变了,不能以返回的方式去体现了。


FutureTask也实现了Runnable的接口,所以第5步使用的Thread构造器还是这个:

public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}

这个start()仍然执行的是实现类NumThread里面的call方法。

对于当前操作来说,有一个返回值Object。如果想获取这个返回值的话,就需要使用到futureTsak

image.png

拿着futureTsak去调用get方法,就能得到总和sum,如下:

image.png

🎲说个容易混淆的事情。

Object sum = futureTask.get();这个操作是在主线程当中做的。

之前说过这样一个例子:

public class ThreadTest {public static void main(String[] args) {// 创建线程对象MyThread t = new MyThread();t.setName("线程1");t.start();// 调用sleep方法try {t.sleep(1000 * 5);} catch (InterruptedException e) {e.printStackTrace();}// 5秒之后这里才会执行。System.out.println("hello World!");}
}class MyThread extends Thread {public void run() {for (int i = 0; i < 10000; i++) {System.out.println(Thread.currentThread().getName() + "--->" + i);}}
}

sleep在哪个线程里面执行,就让哪个线程sleep了。

若是将sleep写在run方法里面了,就是分线程sleep;在主线程里面,就是让主线程sleep了。

还有一种解释,sleep()静态方法,静态方法可以用对象来调用,若对象是null的话也照样可以调。

换句话说,如果将t写成了MyThread,会更加明确。

如下:

image.png

说白了,就是sleep()在哪个线程里面调用的,就让哪个线程sleep


🚗小细节

这里t1.start(),分线程start(),就出去执行了。

image.png

那么主线程会马上执行futureTask.get()吗?

若分线程还没有遍历完100以内的偶数,这时候要是主线程执行的话就会拿不到sum最终的值?

其实不会的。这里会有一个阻塞。这个阻塞是自然而然的。

主线程会一直等分线程return只有分线程return了,主线程才会get

image.png

(3)总结对比

创建多线程的方式三:实现Callable (jdk5.0新增的)

Runnable方式的对比的好处

  • call()可以有返回值,更灵活。
  • call()可以使用throws的方式处理异常,更灵活。
  • Callable使用了泛型参数,可以指明具体的call()的返回值类型(不一定非要是Object),更灵活。

缺点吗?

如果在主线程中需要获取分线程call()返回值,则此时的主线程是阻塞状态的。

image.png

这种方式存在的意义,估计就是笔试题了。

在开发当中,最常使用的就是“线程池”。

二、新增方式2:使用线程池

(1)问题与解决思路

1、现有问题

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,即执行完一个任务,并不被销毁,而是可以继续执行其他的任务

2、解决思路

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中

可以避免频繁创建销毁、实现重复利用。

类似生活中的公共交通工具。

image.png

将创建好的线程都放在一个池子里面,就叫线程池

若此时一系列的任务过来了,每一个任务需要拿一个线程去操作,那么这个任务就可以去线程池里面捞一个线程来用。

假设现在只有三个线程,对应三个任务拿着它们去执行。如果还有其他新的任务,则需要等待。

当现有的任务执行完之后,此时的线程不会因为当前任务的执行结束而销毁,而是又还回到线程池当中,这个线程就可以继续分配给其他任务去执行。

这就得到了很好的重复利用。

当然,我们还可以设置线程池的参数,比如初始化的时候有三个线程池,不够的时候再增加线程。

3、好处

创建多线程的方式四:使用线程池

此方式的好处:

  • 提高了程序执行的效率,提高响应速度。(因为线程已经提前创建好了,减少了创建新线程的时间)
  • 提高了资源的复用率。(因为执行完的线程并未销毁,而是可以继续执行其他的任务)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建) 。
  • 可以设置相关的参数,对线程池中的线程的使用进行管理。(便于线程管理)
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

(2)线程池相关API

  • JDK5.0之前,我们必须手动自定义线程池。从JDK5.0开始,Java内置线程池相关的API。在java.util.concurrent包下提供了线程池相关API:ExecutorServiceExecutors
  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
    • void shutdown() :关闭连接池
  • Executors:一个线程池的工厂类,通过此类的静态工厂方法可以创建多种类型的线程池对象。
    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(int nThreads); 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(int corePoolSize):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

(3)案例

🌱代码

package com.atguigu06.createmore.pool;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;//创建并使用多线程的第四种方法:使用线程池class NumberThread implements Runnable{@Overridepublic void run() {	//指明线程想干什么for(int i = 0;i <= 100;i++){	//打印偶数if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}class NumberThread1 implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){	//打印奇数if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}public class ThreadPool {public static void main(String[] args) {//1. 提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;	//强转
//        //设置线程池的属性
//        System.out.println(service.getClass());//ThreadPoolExecutorservice1.setMaximumPoolSize(50); //设置线程池中线程数的上限//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());//适合适用于Runnableservice.execute(new NumberThread1());//适合适用于Runnable//        service.submit(Callable callable);//适合使用于Callable//3.关闭连接池service.shutdown();}}

🍺输出(部分)

image.png

☕注意

造线程是通过“new Thread”来体现的,这里只是指明了要做的事情,还没有线程的概念。

如下:

image.png

然后通过Executors.newFixedThreadPool(10)造了一个线程池(细节就不用管了),池子里面初始化放了10个线程。

可以设置相关参数:

image.png

现在只需要告诉我你想干什么,就new一个Runnable的实现类的对象,其实就是帮忙调用一下run方法。

image.png

这里没有看到new Thread的代码,因为第1步造线程池的时候已经做好了。

所以就可以将要做的事情的run方法封装在对象里面,往这里的小括号一放,就可以自动执行上面的run方法了。

如下:

image.png

最后,若是这个池子不用了,就可以将它删了。

image.png

线程池也可以适配Callable,Callable如果想要有返回值的话,可以使用submit方法。

在开发当中,如果线程多次使用,其实选择的都是“线程池”的方式。

而这种池子通常要么是直接封装好了,要是用的话,可以像上面案例一样去使用。

具体线程池的使用,在JUC里面说,Java基础阶段不做深入。

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

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

相关文章

回归预测 | Matlab实现RIME-CNN-LSTM-Attention霜冰优化卷积长短期记忆网络注意力多变量回归预测(SE注意力机制)

回归预测 | Matlab实现RIME-CNN-LSTM-Attention霜冰优化卷积长短期记忆网络注意力多变量回归预测&#xff08;SE注意力机制&#xff09; 目录 回归预测 | Matlab实现RIME-CNN-LSTM-Attention霜冰优化卷积长短期记忆网络注意力多变量回归预测&#xff08;SE注意力机制&#xff0…

基于Java SSM框架实现健身后台管理系统项目【项目源码】

基于java的SSM框架实现健身后台管理系统演示 java简介 Java语言是在二十世纪末由Sun公司发布的&#xff0c;而且公开源代码&#xff0c;这一优点吸引了许多世界各地优秀的编程爱好者&#xff0c;也使得他们开发出当时一款又一款经典好玩的小游戏。Java语言是纯面向对象语言之一…

visual studio2022专业版安装步骤

目录 一、Visual studio下载二、创建C#项目——Hello World三、专业版秘钥激活 一、Visual studio下载 首先进入下载官网 先下载2022专业版&#xff0c;等等后面还需要选环境 我勾选了以下几个和c#开发有关的&#xff0c;后面缺什么还可以再安装所有以少勾了问题也不大 然后…

AJAX-URL查询参数

定义&#xff1a;浏览器提供给服务器的额外信息&#xff0c;让服务器返回浏览器想要的数据 http://xxxx.com/xxx/xxx?参数名1值1&参数名2值2 axios语法 使用axios提供的params选项 注意&#xff1a;axios在运行时把参数名和值&#xff0c;会拼接到url?参数名值 axios(…

初始并查集

目录 一.并查集的介绍 二.并查集核心函数介绍 1.查找函数Find() 2.合并函数Union() 一.并查集的介绍 并查集&#xff08;Disjoint-Set Union&#xff0c;简称并查集&#xff09;是一种用来管理集合的数据结构。它主要支持两种操作&#xff1a; 1.查找&#xff08;Find&…

LabVIEW叶片厚度远程监控

LabVIEW叶片厚度远程监控 随着网络技术的高速发展&#xff0c;远程监控广泛应用在各个领域。本文介绍了一种基于LabVIEW的植物叶片厚度远程监控系统&#xff0c;旨在实现对植物生长状况的精准监测和分析。 该系统利用LabVIEW软件开发工具&#xff0c;通过TCP网络协议实现数据…

一文读懂「LM,Large Model」大模型

有时候我们经常说行业大模型&#xff0c;医疗大模型&#xff0c;开源大模型&#xff0c;甚至用「产品大模型」的固定结构去称呼一个模型&#xff0c;例如百度的文心一言大模型&#xff0c;但是文心一言其实是大语言模型&#xff0c;大模型和大语言模型&#xff0c;差别就两个字…

如何手机搜中国大学mooc答案?推荐9个搜题软件和学习工具 #经验分享#其他

以下软件拥有强大的搜索功能&#xff0c;能够快速找到与题目相关的资料和答案&#xff0c;让大学生们更容易理解和掌握知识点。 1.Google翻译 可提供简体中文和另外 100 多种语言之间的互译功能&#xff0c;可让您即时翻译字词、短语和网页内容 Google的免费翻译服务 2.大鱼…

如何在docker中访问电脑上的GPU?如何在docker中使用GPU进行模型训练或者加载调用?

如何在docker中访问电脑上的GPU&#xff1f;如何在docker中使用GPU进行模型训练或者加载调用&#xff1f; 其实使用非常简单&#xff0c;只是一行命令的事&#xff0c;最主要的事配置好驱动和权限。 docker run -it --rm --gpus all ycj520/centos:1.0.0 nvidia-smi先看看 st…

基于YOLOv8深度学习的水稻叶片病害智能诊断系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

C语言指针学习 之 指针变量

前言&#xff1a; 通过学习我们认识了什么是指针&#xff0c;就让我们一起来分析一个例子。 #include<stdio.h> int main() {int a100;int * hz; hz &a;printf("a%d \n",a);printf("*hz%d \n",*hz);return 0; }a100 *hz100 PS C:\csay\cyuya…

Facebook的社交影响力:用户行为解析与趋势

在当今数字时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分&#xff0c;而Facebook作为全球最大的社交平台之一&#xff0c;其社交影响力愈发显著。本文将深入分析Facebook的社交影响力&#xff0c;解析用户行为&#xff0c;同时探讨当前和未来的社交趋势。 社…