当前位置: 首页 > news >正文

【线程】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());}});}}
}

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

相关文章:

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

【线程】Java多线程代码案例&#xff08;2&#xff09; 一、定时器的实现1.1Java标准库定时器1.2 定时器的实现 二、线程池的实现2.1 线程池2.2 Java标准库中的线程池2.3 线程池的实现 一、定时器的实现 1.1Java标准库定时器 import java.util.Timer; import java.util.Timer…...

虚拟机之间复制文件

在防火墙关闭的前提下&#xff0c;您可以通过几种不同的方法将文件从一个虚拟机复制到另一个虚拟机。这里&#xff0c;我们假设您想要从 IP 地址为 192.168.4.5 的虚拟机上的 /tmp 文件夹复制文件到当前虚拟机&#xff08;192.168.4.6&#xff09;的 /tmp 文件夹下。以下是几种…...

如何为 XFS 文件系统的 /dev/centos/root 增加 800G 空间

如何为 XFS 文件系统的 /dev/centos/root 增加 800G 空间 一、前言二、准备工作三、扩展逻辑卷1. 检查现有 LVM 配置2. 扩展物理卷3. 扩展卷组4. 扩展逻辑卷四、调整文件系统大小1. 检查文件系统状态2. 扩展文件系统五、处理可能出现的问题1. 文件系统无法扩展2. 磁盘空间不足3…...

Java算法OJ(11)双指针练习

目录 1.前言 2.正文 2.1存在重复数字 2.1.1题目 2.1.2解法一代码 解析&#xff1a; 2.1.3解法二代码 解析&#xff1a; 2.2存在重复数字plus 2.2.1题目 2.2.2代码 2.2.3解析 3.小结 1.前言 哈喽大家好吖&#xff0c;今天来给大家分享双指针算法的相关练习&…...

44.扫雷第二部分、放置随机的雷,扫雷,炸死或成功 C语言

按照教程打完了。好几个bug都是自己打出来的。比如统计周围8个格子时&#xff0c;有一个各自加号填成了减号。我还以为平移了&#xff0c;一会显示是0一会显示是2。结果单纯的打错了。debug的时候断点放在scanf后面会顺畅一些。中间多放一些变量名方便监视。以及mine要多显示&a…...

大语言模型LLM的微调代码详解

代码的摘要说明 一、整体功能概述 这段 Python 代码主要实现了基于 Hugging Face Transformers 库对预训练语言模型&#xff08;具体为 TAIDE-LX-7B-Chat 模型&#xff09;进行微调&#xff08;Fine-tuning&#xff09;的功能&#xff0c;使其能更好地应用于生成唐诗相关内容的…...

钉钉与企业微信机器人:助力网站定时任务高效实现

钉钉、企业微信机器人在网站定时任务中的应用&#xff0c;主要体现在自动化通知、提醒以及数据处理等方面。 以下是一些具体的应用场景&#xff1a; 1. 自动化通知 项目进度提醒&#xff1a;在蒙特网站所负责的软件开发或网站建设项目中&#xff0c;可以利用机器人设置定时任…...

自然语言处理工具-广告配音工具用于语音合成助手/自媒体配音/广告配音/文本朗读-已经解锁了 全功能的 apk包

Android -「安卓端」 广告配音工具用于语音合成助手/自媒体配音/广告配音/文本朗读。 广告配音工具&#xff1a;让您的文字“说话”&#xff0c;在这个快速发展的数字时代&#xff0c;广告配音工具为各种语音合成需求提供了一站式解决方案。无论是自媒体配音、商业广告配音、…...

深入解析注意力机制

引言随着深度学习的快速发展&#xff0c;注意力机制&#xff08;Attention Mechanism&#xff09;逐渐成为许多领域的关键技术&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;和计算机视觉&#xff08;CV&#xff09;中。其核心思想是赋予模型“关注重点”的能力…...

Unity图形学之雾Fog

1.设置雾化&#xff1a; 2.雾化变化曲线&#xff1a;FogMode &#xff08;1&#xff09;线性&#xff1a; &#xff08;2&#xff09;一次指数&#xff1a; &#xff08;3&#xff09;二次指数&#xff1a; Shader "Custom/FogTest" {Properties{_Color ("Color…...

【大数据学习 | Spark-Core】详解Spark的Shuffle阶段

1. shuffle前言 对spark任务划分阶段&#xff0c;遇到宽依赖会断开&#xff0c;所以在stage 与 stage 之间会产生shuffle&#xff0c;大多数Spark作业的性能主要就是消耗在了shuffle环节&#xff0c;因为该环节包含了大量的磁盘IO、序列化、网络数据传输等操作。 负责shuffle…...

如何启动 Docker 服务:全面指南

如何启动 Docker 服务:全面指南 一、Linux 系统(以 Ubuntu 为例)二、Windows 系统(以 Docker Desktop 为例)三、macOS 系统(以 Docker Desktop for Mac 为例)四、故障排查五、总结Docker,作为一种轻量级的虚拟化技术,已经成为开发者和运维人员不可或缺的工具。它允许用…...

使用client-go在命令空间test里面对pod进行操作

目录 一、获取使用restApi调用的token信息 二、client-go操作pod示例 1、获取到客户端 2、创建pod 3、获取test命令空间的所有pod 4、获取某个具体pod的详细信息 5、更新pod 6、删除pod 三、总结 官方参考地址&#xff1a;https://kubernetes.io/docs/reference/kuber…...

Linux中网络文件系统nfs使用

一、nfs服务 NFS&#xff08;Network File System&#xff09; 是一种用于在网络中共享文件的协议&#xff0c;允许不同操作系统&#xff08;如 Linux、Unix、MacOS 等&#xff09;之间进行文件共享。 NFS 的工作原理基于客户端-服务器模型&#xff0c;服务器提供共享文件系统…...

气膜建筑:打造全天候安全作业空间,提升工程建设效率—轻空间

在现代建筑工程中&#xff0c;施工环境的管理和作业效率是决定项目进度和质量的关键因素。然而&#xff0c;施工过程中常常会受到天气变化的影响&#xff0c;诸如大风、雨雪、沙尘等恶劣天气常常延误工期&#xff0c;增加施工难度。为了解决这一问题&#xff0c;气膜建筑以其独…...

【HarmonyOS学习日志(10)】一次开发,多端部署之功能级一多开发,工程级一多开发

功能级一多开发 SysCap机制介绍 HarmonyOS使用SysCap机制&#xff08;即SystemCapability&#xff09;&#xff0c;可以帮助开发者仅关注设备的系统能力&#xff0c;而不用考虑成百上千种具体的设备类型。 在过去&#xff0c;开发不同设备上的应用就用不同设备的SDK进行开发&…...

dmdba用户资源限制ulimit -a 部分配置未生效

dmdba用户资源限制ulimit -a 部分配置未生效 1 环境介绍2 数据库实例日志报错2.1 mpp01 实例日志报错2.2 mpp02 实例日志报错 3 mpp02 服务器资源限制情况4 关闭SELinux 问题解决4.1 临时关闭 SELinux4.2 永久关闭 SELinux 5 达梦数据库学习使用列表 1 环境介绍 Cpu x86 Os Ce…...

【Code First】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列 &#x1f…...

如何在谷歌浏览器中切换DNS服务器

在浏览网页时&#xff0c;DNS&#xff08;域名系统&#xff09;服务器的作用是将您输入的网址转换为计算机可以理解的IP地址。有时&#xff0c;您可能需要更改默认的DNS服务器以提升网络速度或解决访问问题。本文将详细介绍如何在谷歌浏览器中切换DNS服务器&#xff0c;并在此过…...

Spring Cloud Stream实现数据流处理

1.什么是Spring Cloud Stream&#xff1f; Spring Cloud Stream的核心是Stream&#xff0c;准确来讲Spring Cloud Stream提供了一整套数据流走向&#xff08;流向&#xff09;的API&#xff0c; 它的最终目的是使我们不关心数据的流入和写出&#xff0c;而只关心对数据的业务处…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

Web 架构之 CDN 加速原理与落地实践

文章目录 一、思维导图二、正文内容&#xff08;一&#xff09;CDN 基础概念1. 定义2. 组成部分 &#xff08;二&#xff09;CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 &#xff08;三&#xff09;CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 &#xf…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

无人机侦测与反制技术的进展与应用

国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机&#xff08;无人驾驶飞行器&#xff0c;UAV&#xff09;技术的快速发展&#xff0c;其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统&#xff0c;无人机的“黑飞”&…...