【线程】Java多线程代码案例(2)

【线程】Java多线程代码案例(2)

      • 一、定时器的实现
        • 1.1Java标准库定时器
        • 1.2 定时器的实现
      • 二、线程池的实现
        • 2.1 线程池
        • 2.2 Java标准库中的线程池
        • 2.3 线程池的实现

一、定时器的实现

1.1Java标准库定时器
import java.util.Timer;
import java.util.TimerTask;public class ThreadDemo5 {public static void main(String[] args) throws InterruptedException {Timer timer =new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000");}},1000);System.out.println("hello main");}
}
1.2 定时器的实现

首先考虑,定时器中都需要都需要实现哪些元素呢?

  1. 需要有一个线程,负责掐时间
  2. 还需要有一个队列,能够保存所有添加进来的任务,这个队列要带有阻塞功能
    因为这个任务,要先执行时间小的,再执行时间大的。此处我们可以实现一个优先级队列。那么时间小的任务就始终排在第一位,我们只需要关注队首元素是否到时间,如果队首没有到时间,那么后续其他元素,也一定没有到时间。

首先定义任务类,包含要执行的任务和时间

class MyTimerTask implements Comparable<MyTimerTask>{//执行时间private long time;//持有一个Runnableprivate Runnable runnable;public MyTimerTask(Runnable runnable,long delay){this.time=System.currentTimeMillis()+delay;this.runnable=runnable;}//实际要执行的任务public void run(){runnable.run();}public long getTime() {return time;}@Override//因为要加入优先级队列,必须能比较public int compareTo(MyTimerTask o) {return (int)(this.time-o.time);}
}

定义计时器

class MyTimer{//持有一个线程负责计时private Thread t=null;//优先级队列private PriorityQueue<MyTimerTask> queue =new PriorityQueue<>();//前面实现阻塞队列的逻辑,加锁private Object locker =new Object();//添加任务public void schedule(Runnable runnable,long delay){}//构造方法//注意执行任务并不需要我们写一个方法在main()函数中调用//这个是到时间自动执行的public MyTimer(){t=new Thread(()->{while(true){//到时间执行任务的逻辑}});}
}

那接下来我们就来分别实现这里的schedule方法和构造函数中执行任务的逻辑:
schedule():

public void schedule(Runnable runnable,long delay){//入队列和出队列都需要打包成“原子性”的操作,加锁实现synchronized(locker){//新建任务MyTimerTask task=new MyTimerTask(runnable,delay);//加入队列queue.offer(task);//参考前面阻塞队列的实现,当队列为空时wait(),加入元素后notify()locker.notify();}
}

构造方法:

public MyTimer(){t=new Thread(()->{while(true){try{synchronized(locker){while(queue.isEmpty()){//阻塞直到加入新的任务后被notify()唤醒locker.wait();}//查看队首元素//peek不会将元素弹出MyTimerTask task=queue.peek;if(System.currentTimeMillis() >= task.getTime()){queue.poll();task.run();}else{//阻塞,释放锁(允许继续添加任务)//设置最大阻塞时间,阻塞到这个时间到了locker.wait(task.getTime()-System.currentTimeMillis());}}catch (InterruptedException e) {break;}}});	//启动线程t.start();
}

写到这里,就大功告成了,我们在main()函数中试验看一下运行结果:

public class ThreadDemo5{public static void main(String[] args) {MyTimer timer=new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println(3000);}},3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println(2000);}},2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println(1000);}},1000);Thread.sleep(4000);timer.cancel();}
}

这里我们再加一个方法,我们希望任务执行完成后,能够主动结束这个线程:

public void cancel(){t.interrupt();
}

这里需要考虑线程被提前唤醒抛出的异常,因此在构造方法中将捕获异常的操作改为break;
在这里插入图片描述
计时器完整代码:

import java.util.PriorityQueue;class MyTimerTask implements Comparable<MyTimerTask>{//执行时间private long time;//持有一个Runnableprivate Runnable runnable;public MyTimerTask(Runnable runnable,long delay){this.time=System.currentTimeMillis()+delay;this.runnable=runnable;}//实际要执行的任务public void run(){runnable.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTimerTask o) {return (int)(this.time-o.time);}
}class MyTimer{//持有一个线程负责计时private Thread t=null;//任务队列——>优先级队列private PriorityQueue<MyTimerTask> queue =new PriorityQueue<>();//锁对象private Object locker=new Object();public void schedule(Runnable runnable,long delay){synchronized (locker) {//新建任务MyTimerTask task = new MyTimerTask(runnable, delay);//加入队列queue.offer(task);locker.notify();}}public void cancel(){t.interrupt();}public MyTimer(){t = new Thread(() -> {while (true) {try {synchronized (locker) {while (queue.isEmpty()) {//阻塞locker.wait();}//查看队首元素MyTimerTask task = queue.peek();if (System.currentTimeMillis() >= task.getTime()) {queue.poll();task.run();} else {//阻塞locker.wait(task.getTime()-System.currentTimeMillis());}}} catch (InterruptedException e) {break;}}});t.start();}
}
public class ThreadDemo5{public static void main(String[] args) throws InterruptedException {MyTimer timer=new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println(3000);}},3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println(2000);}},2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println(1000);}},1000);Thread.sleep(4000);timer.cancel();}
}

二、线程池的实现

2.1 线程池

最初我们提到线程这个概念,其实是一个“轻量级进程”。他的优势在于无需频繁地向系统申请/释放内存,提高了效率。但是随着线程的增多,频繁地创建/销毁线程也是一个很大的开销。解决方案有两种:

  1. 轻量级线程(协程),Java 21中引入了虚拟线程,就是这个东西。协程主要在Go语言中有较好的运用。
  2. 其次就是引入线程池的概念,无需频繁创建/销毁线程,而是一次性的创建好许多线程,每次直接取用,用完了放回线程池中。

为什么从线程池里取线程,会比从系统中申请更高效。
本质上在于去线程池里取线程,是一个用户态的操作,而向系统申请线程是一个内核态的操作。
在这里插入图片描述
还是以去银行取钱为例,向系统申请线程,就相当于找工作人员,在柜台取钱(工作人员收到请求后可能不会立即给你取钱),相对低效;而从线程池中取用线程,则相当于从ATM机里面取钱(从ATM机里面取钱是可以立即取到的),相对高效。

2.2 Java标准库中的线程池

在这里插入图片描述
这里我们可以细看一下这里的参数:

  1. corePoolSize(核心线程数)
    一个线程池里,最少要有多少个线程,相当于正式工,不会被销毁。
  2. maximumPoolSize(最大线程数)
    一个线程池里,最多要有多少个线程,相当于临时工,一段时间不干活就被销毁。
  3. keepAliveTime
    临时工允许的空闲时间,超过这个时间,就被销毁。
  4. unit
    keepAliveTime的时间单位
  5. BlockingQueue workQueue
    传递任务的阻塞队列
  6. threadFactory
    创建线程的工厂,参与具体的创建线程的工作。
    这里涉及到工厂模式,试想这样的代码能否运行:
class Point{//笛卡尔坐标系public point(double x,double y){...}//极坐标系public point(double r,double a){...}
}

像这样的代码是无法运行的。因为他们具有相同的方法名和参数列表,无法完成重载。那如果确实想完成这样的操作,该怎么做呢?

class Point{public static Point makePointByXY(double x, double y){Point p=new Point();p.setX(x);p.setY(y);return p;}public static Point makePointByRA(double r,double a){Point p=new Point();p.setR(r);p.setA(a);return p;}
}
Point p=Point.makePointByXY(x,y);
Point p=Point.makePointByRA(r,a);

总的来说,通过静态方法封装new操作,在方法内部设定不同的属性完成对象的初始化,构造对象的过程,就是工厂模式。

  1. RejectedExecutionHandler handler
    拒绝策略。如果这里的阻塞队列满了,此时要添加任务,就需要有一个应对策略。
策略含义备注
AbortPolicy()超过负荷,抛出异常所有任务都不做了
CallerRunsPolicy()调用者负责处理多出来的任务所有任务都要做,新加的任务由添加任务的线程做
DiscardOldestPolicy()丢弃队列中最老的任务不做最老的任务
DiscardPolicy()丢弃新来的任务不做最新的任务

由于ThreadPoolExecutor本身用起来比较复杂,因此标准库还提供了一个版本,把ThreadPoolExecutor给封装了一下。Executors 工厂类,通过这个类来创建不同的线程池对象(内部把ThreadPoolExecutor创建好了并且设置了不同的参数)
大致有这么几种方法:

方法用途
newScheduleThreadExecutor()创建定时器线程,延时执行任务
newSingleThreadExecutor()只包含单个线程的线程池
newCachedThreadExecutor()线程数目能够动态扩容
newFixedThreadExecutor()线程数目固定
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadDemo6 {public static void main(String[] args) {ExecutorService service=Executors.newFixedThreadPool(4);service.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});}
}

那么,对于一个多线程任务,创建多少个线程合适呢?

  1. 如果任务都是CPU密集型的(大部分时间在CPU上执行),此时线程数不应超过逻辑核心数;
  2. 如果任务都是IO密集型的(大部分时间在等待IO),此时线程数可以远远超过逻辑核心数;
  3. 由于实际的任务都是两种任务混合型的,一般通过实验的方式来得到最合适的线程数。
2.3 线程池的实现

我们可以实现一个简单的线程池(固定线程数目的线程池),要完成以下任务:

  1. 提供构造方法,指定创建多少个线程;
  2. 在构造方法中,创建线程;
  3. 有一个阻塞队列,能够执行要执行的任务;
  4. 提供submit()方法,添加新的任务
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;class MyThreadPoolExecutor{private List<Thread> threadList=new ArrayList<>();//阻塞队列private BlockingQueue<Runnable> queue=new ArrayBlockingQueue<>(10);public MyThreadPoolExecutor(int n){for(int i=0;i<n;i++){Thread t=new Thread(()-> {while (true) {try {//take操作也带有阻塞Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();threadList.add(t);}}public void submit(Runnable runnable) throws InterruptedException {//put操作带有阻塞功能queue.put(runnable);}
}
public class ThreadDemo6 {public static void main(String[] args) throws InterruptedException {MyThreadPoolExecutor executor=new MyThreadPoolExecutor(4);for(int i=0;i<1000;i++){int n=i;executor.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行任务:"+n+",当前线程:"+Thread.currentThread().getName());}});}}
}

运行结果
在这里插入图片描述

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

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

相关文章

非递归遍历二叉树(数据结构)

我的博客主页 非递归遍历二叉树 前序遍历&#xff08;迭代&#xff09;中序遍历&#xff08;迭代&#xff09;后续遍历&#xff08;迭代&#xff09; 二叉树的遍历方式有&#xff1a;前序遍历、中序遍历、后续遍历&#xff0c;层序遍历&#xff0c;而树的大部分情况下都是通过递…

【CSS】一篇掌握CSS

不是因为有了希望才去坚持,而是坚持了才有了希望 目录 一.导入方式 1.行内样式 2.内部样式 3.外部样式(常用) 二.选择器 1.基本选择器(常用) 1.1标签选择器 1.2类选择器 1.3id选择器 2.层次选择器 2.1后代选择器 2.2子选择器 2.3相邻兄弟选择器 2.4通用兄弟选择器…

封装类与封装函数

目录结构 src/ ├── utils/ │ ├── test.js │ ├── Calculator.js ├── views/ │ ├── Home.vue ├── App.vue 共同点&#xff1a;模块导出与模块引入 封装函数 场景 简单、轻量级和性能敏感的场景&#xff0c;适合快速开发和维护。 优 可维护性&…

【论文阅读】Federated learning backdoor attack detection with persistence diagram

目的&#xff1a;检测联邦学习环境下&#xff0c;上传上来的模型是不是恶意的。 1、将一个模型转换为|L|个PD,&#xff08;其中|L|为层数&#xff09; 如何将每一层转换成一个PD&#xff1f; 为了评估第&#x1d457;层的激活值&#xff0c;我们需要&#x1d450;个输入来获…

5 Java字符串操作

字符串操作 1、String类1.1 声明字符串1.2 创建字符串 1.3 字符串连接 /连接字符串连接其他数据类型 1.4 提取字符串信息获取字符串长度length()获取指定位置的字符 charAt()获取子字符串索引位置 indexOf()判断字符串首尾内容 startsWith()/endsWith()获取字符数组 toCharArra…

IDEA报错: java: JPS incremental annotation processing is disabled 解决

起因 换了个电脑打开了之前某个老项目IDEA启动springcloud其中某个服务直接报错&#xff0c;信息如下 java: JPS incremental annotation processing is disabled. Compilation results on partial recompilation may be inaccurate. Use build process “jps.track.ap.depen…

Mybatis-基础操作

Mybatis的基础操作就是通过Mybatis完成对数据的增删改查。我们通过例子来引入这些操作&#xff0c;之前的项目较久远&#xff0c;因此我们从零开始进行准备工作&#xff1a; 搭建项目 一、创建数据库user_list并插入数据&#xff1a; -- 创建数据库 create table user_list …

火山引擎VeDI在AI+BI领域的演进与实践

随着数字化时代的到来&#xff0c;企业对于数据分析与智能决策的需求日益增强。作为新一代企业级数据智能平台&#xff0c;火山引擎数智平台VeDI基于字节跳动多年的“数据驱动”实践经验&#xff0c;也正逐步在AI&#xff08;人工智能&#xff09;与BI&#xff08;商业智能&…

鼠标前进后退键改双击,键盘映射(AutoHotkey)

初衷&#xff1a; 1.大部分鼠标为不可自定义按键&#xff0c;可以自定义的又很贵。 鼠标左键是双击是很频类很高的操作&#xff0c;鼠标前进/后退按键个人感觉使用频率很低&#xff0c;因此把鼠标前进/后退改为双击还是很合适的。 2.有些短款的键盘没有Home或End键&#xff0c;…

IntelliJ IDEA安装内网穿透实现远程连接家里或公司的MySQL数据库助力开发

文章目录 前言1. 本地连接测试2. Windows安装Cpolar3. 配置Mysql公网地址4. IDEA远程连接Mysql5. 固定连接公网地址6. 固定地址连接测试 前言 本教程主要介绍如何使用Cpolar内网穿透工具实现在IDEA中也可以远程访问家里或者公司的数据库&#xff0c;提高开发效率&#xff01;无…

联想品牌的电脑 Bios 快捷键是什么?如何进入 Bios 设置?

在某些情况下&#xff0c;您可能需要通过U盘来安装操作系统或进行系统修复。对于联想电脑用户来说&#xff0c;了解如何设置U盘作为启动设备是非常有用的技能之一。本文简鹿办公将指导您如何使用联想电脑的 U 盘启动快捷键来实现这一目标。 联想笔记本 对于大多数联想笔记本电…

MCU跨领域融合的风向标是什么?

【哔哥哔特导读】从市场竞争的加剧到技术发展的需求&#xff0c;从智能化趋势到安全性要求的提高&#xff0c;再到市场需求的变化&#xff0c;这些因素共同推动了MCU趋势的发展&#xff0c;那么&#xff0c;当前的发展方向是怎样的&#xff1f; 随着技术的飞速发展和市场需求的…

【Android+多线程】IntentService 知识总结:应用场景 / 使用步骤 / 源码分析

定义 IntentService 是 Android中的一个封装类&#xff0c;继承自四大组件之一的Service 功能 处理异步请求 & 实现多线程 应用场景 线程任务 需 按顺序、在后台执行 最常见的场景&#xff1a;离线下载不符合多个数据同时请求的场景&#xff1a;所有的任务都在同一个T…

AI自动化剪辑工具:可将长视频中精彩部分提取合成短视频

最近&#xff0c;我发现了一款特别适合当下短视频潮流的自动化工具&#xff0c;它能够让我们轻松从长视频中剪辑出精彩片段&#xff0c;并快速生成适合分享的短视频。 这款工具叫 AI Youtube Shorts Generator&#xff0c;是一个开源项目&#xff0c;特别适合那些喜欢制作短视…

Basemap 在地图上显示图例

1.卫星图像绘制 import matplotlib.pyplot as plt from mpl_toolkits.basemap import Basemap # 图像绘制 plt.figure(dpi300) m Basemap(projectioncyl, llcrnrlat11, llcrnrlon105, urcrnrlat35, urcrnrlon135)raw_lat raw_lat[490:1080, 655:1470] raw_lon raw_lon[490:…

GitLab历史演进

GitLab 是一个基于 Git 的 DevOps 平台&#xff0c;它的历史演进反映了开发和运维协作工具的不断发展。GitLab 的目标是为开发团队提供一个集成的工具集&#xff0c;涵盖 源代码管理、CI/CD、项目管理 等功能。GitLab 最初只是一个 Git 仓库管理工具&#xff0c;但随着时间的推…

elasticsearch单节点模式部署

原文地址&#xff1a;elasticsearch单节点模式部署 – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 第一步&#xff1a;下载 官方下载地址&#xff1a;Download Elasticsearch | Elastic&#xff0c;可以 wget 直接下载。 命令&#xff1a;wg…

26页PDF | 数据中台能力框架及评估体系解读(限免下载)

一、前言 这份报告详细解读了数据中台的发展历程、核心概念、能力框架及成熟度评估体系。它从阿里巴巴的“大中台&#xff0c;小前台”战略出发&#xff0c;探讨了数据中台如何通过整合企业内部的数据资源和能力&#xff0c;加速业务迭代、降低成本&#xff0c;并推动业务增长…

音视频入门基础:MPEG2-TS专题(8)——TS Header中的适配域

注&#xff1a;本文有部分内容引用了维基百科&#xff1a;https://zh.wikipedia.org/wiki/MPEG2-TS 一、引言 当TS Header中的adaptation_field_control属性的值为10或11 时&#xff0c;TS Header包含adaptation field&#xff08;适配域&#xff09;&#xff1a; 根据《T-RE…

挑战用React封装100个组件【001】

项目地址 https://github.com/hismeyy/react-component-100 组件描述 组件适用于需要展示图文信息的场景&#xff0c;比如产品介绍、用户卡片或任何带有标题、描述和可选图片的内容展示 样式展示 代码展示 InfoCard.tsx import ./InfoCard.cssinterface InfoCardProps {ti…