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

Java 多线程(三)

文章目录

  • 多线程的代码案例
    • 定时器
    • 实现一个定时器
    • 线程池
      • Java标准库中的线程池使用
      • 模拟实现一个简单的线程池

多线程的代码案例

定时器

  1. 在标准库中也是有现成的定时器实现的
  2. 给定时器安排某个任务,让它在某个时间去执行
  3. 定时器让这个程序稍等一会才会执行
import java.util.Timer;
import java.util.TimerTask;public class Demo24 {public static void main(String[] args) {Timer timer = new Timer();// 给定时器安排某个任务,让它在某个时间去执行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("执行定时器的任务!");}},2000);System.out.println("程序启动!");}
}

在这里插入图片描述

在这里插入图片描述
4. Timer里可以安排多个任务

import java.util.Timer;
import java.util.TimerTask;public class Demo24 {public static void main(String[] args) {Timer timer = new Timer();// 给定时器安排某个任务,让它在某个时间去执行timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3000!");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000!");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000!");}},1000);System.out.println("程序启动!");}
}

实现一个定时器

  1. 实现步骤:
  2. 使用哪个数据结构更好呢?
    可以使用优先级队列,根据时间小的任务优先执行

一个线程扫描任务
一个数据结构存储任务
一个类描述任务内容和时间

  1. 使用time保存一个绝对的时间戳,可以更好地进行后续的比较,而相对时间戳需要换算
    在这里插入图片描述
    在这里插入图片描述

Runnable 是一个接口(Interface),它代表了一段可以被线程执行的代码任务

实现定时器的代码:

import java.util.PriorityQueue;
import java.util.TimerTask;// 通过这个类,描述这个任务
class MyTimeTask implements Comparable<MyTimeTask> {// 描述一个任务private Runnable runnable;// 执行任务的时间private long time;// 构造方法,此处的delay就是schedule传入的相对时间public MyTimeTask(Runnable runnable,long delay){this.runnable = runnable;this.time = System.currentTimeMillis() + delay;}@Overridepublic int compareTo(MyTimeTask o) {return (int) (this.time - o.time);}public long getTime(){return time;}public Runnable getRunnable(){return runnable;}
}// 搞一个定时器的类
class MyTimer{// 使用一个数据结构保存所有的任务PriorityQueue<MyTimeTask> queue = new PriorityQueue<>();// 使用这个对象作为锁对象private Object locker = new Object();// 主线程对队列进行修改,添加了新的元素public void schedule(Runnable runnable,long delay){synchronized (locker) {queue.offer(new MyTimeTask(runnable, delay));locker.notify();}}// 搞一个线程扫描队列public MyTimer(){Thread t1 = new Thread(()->{// 扫描线程对队列进行了修改// 扫描线程,看一个线程的队首元素是否,看是否是到达了时间while (true) {try {synchronized (locker) {// 使用while,在后续线程被唤醒时,再次确认一下条件while (queue.isEmpty()) {// 阻塞等待,用wait进行等待// 这里的wait应该由其他线程唤醒// 添加了任务就应该唤醒locker.wait();}MyTimeTask task = queue.peek();// 比较一下当前的对首元素是否可以执行了long curTime = System.currentTimeMillis();if(curTime >= task.getTime()){// 当前时间达到了任务时间可以进行任务了task.getRunnable().run();// 完成任务,把任务从队列中删除掉queue.poll();}else{// 当前时间还没有到任务时间不执行任务// 什么都不干,等待下一轮的时间判定// 等待这个时间差locker.wait(task.getTime() - curTime);}// 还可以写一个如果开始的delay时间是负数,那么本身现在时间// 大于任务时间,就提示错误}} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();}
}
public class Demo25 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3000!");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("2000!");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1000!");}},1000);System.out.println("程序启动!");}
}

两个线程对同一个队列进行操作,线程是不安全的,需要加锁
在这里插入图片描述
3. 调试的问题:
在这里插入图片描述
4. 这里还有一个问题:忙等
在这里插入图片描述
如何解决这个忙等呢?
需要有一个超时时间的wait等待,等待到下一个任务时间的开始,超时时间为下次任务时间减现在的系统时间

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

线程池

  1. 虽然线程对比进程是更高效了,但是线程频繁的创建和销毁,这样的开销也是不容忽视的

  2. 有两种办法可以进一步提高线程的效率:
    协程(轻量级线程):相比于线程,把系统调度的步骤给省略了,需要我们自己调度。协程是当下比较流行的并发编程的手段,但是java中不流行

    线程池也是一种手段

  3. 线程池的优点:在使用第一个线程的时候,就把2,3,4,5线程创建好,后续想要使用新的线程就不必重新创建了,直接拿过来就能用(此时创建线程的开销就被降低了)

  4. 为什么从池子里面取的效率比创建线程的效率更高?
    因为用户态只需要给自己去做,效率更高,而内核态是cpu要给所有线程提供服务,效率自然就低了,从池子里取只有用户态,创建线程有用户态 + 内核态
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

Java标准库中的线程池使用

  1. 线程池不是直接new,而是通过一种专门的方法,返回一个线程池对象
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Demo27 {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(4);// 给线程池添加任务service.submit(new Runnable() {@Overridepublic void run() {System.out.println("world");}});}
}
  1. 线程池的创建又涉及到了工厂模式
  2. 通常我们创建对象用new,new关键字会触发构造方法,但是构造方法存在局限性,工厂模式就是给构造方法填坑的

填什么坑呢?
举个例子:
在这里插入图片描述
在这里插入图片描述
4. 四种类型的线程池:(前两个比较常用)
第一种cached是动态适应的线程池数量
第二种fixed是申请固定数量的线程池
在这里插入图片描述
newSingleThreadExecutor(),只有一个线程的线程池(用的不多)
newScheduledThreadPool(int corePoolSize),相当于定时器,不是一个线程扫描执行任务了,而是多个线程执行时间到的任务了

在这里插入图片描述
5. ThreadPoolEexcutor
的主要方法有两个:
构造
注册任务(添加任务)

ThreadPoolEexcutor的构造方法的参数有很多(重点),经典面试题

  1. java.util.concurrent:关于并发编程的(在java中主要是多线程)
  2. 对参数的解释:

在这里插入图片描述
正式员工用完也不会被销毁,实习生用完并且不用你了就会被销毁

描述线程数目:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
拒绝策略:
在这里插入图片描述
不同的拒绝策略:
在这里插入图片描述
拒绝策略的例子:

在这里插入图片描述
8. 线程数目和拒绝策略(面试必考)

  1. 线程池的数量如何设置最合适?

在这里插入图片描述
正确的做法应该是使用实验的方式,对程序进行性能测试,实验中使用不同的线程池个数进行测试,看哪种情况下最符合你的需求

模拟实现一个简单的线程池

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;class MyThreadPool{// 任务队列private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);// 添加任务public void submit(Runnable runnable) throws InterruptedException {// 此处相当于拒绝策略了,就是第五种策略,阻塞等待了queue.put(runnable);}public MyThreadPool(int n){// 创建出n个线程负责上述任队列中的任务for(int i = 0;i < n;i++) {Thread t = new Thread(() -> {try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();}}
}
public class Demo28 {public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool = new MyThreadPool(4);for(int i = 0;i < 1000;i++){int id = i;myThreadPool.submit(new Runnable() {@Overridepublic void run() {// 在匿名内部类中涉及了变量捕获问题,id就是System.out.println("执行任务 + " + id);}});}}
}

在这里插入图片描述

此时这里的id每次循环都是一个新的id,并且每次循环都会赋值上一个初值,所以说每次的id是一个不变的值(final)


文章转载自:

http://CLpIKmz6.kpxky.cn
http://4xzVW6O6.kpxky.cn
http://MotPzhPo.kpxky.cn
http://bbnvrs5G.kpxky.cn
http://YGVjsPdW.kpxky.cn
http://UtG0LAVe.kpxky.cn
http://r0EoSdBL.kpxky.cn
http://h4baLyaY.kpxky.cn
http://qkSorh91.kpxky.cn
http://BIhaBEC3.kpxky.cn
http://OIyTn7dA.kpxky.cn
http://QDDxzlDm.kpxky.cn
http://oSpoARJE.kpxky.cn
http://E8Ki2bun.kpxky.cn
http://V8mmz649.kpxky.cn
http://bU7qXq2N.kpxky.cn
http://LGFWIUEH.kpxky.cn
http://4AwYeqAp.kpxky.cn
http://n4GAg4mH.kpxky.cn
http://gH3eJ7tf.kpxky.cn
http://rqOYUf5A.kpxky.cn
http://f7QQL9zq.kpxky.cn
http://FneVvfwB.kpxky.cn
http://wMOTAewr.kpxky.cn
http://yJjaTST9.kpxky.cn
http://KlUaph8l.kpxky.cn
http://gloqfB8d.kpxky.cn
http://rHi9e6zQ.kpxky.cn
http://0svRqKkJ.kpxky.cn
http://78gKeZ2j.kpxky.cn
http://www.dtcms.com/a/380706.html

相关文章:

  • 【tips】el-input-number 数字输入框初始值超出限制值后,v-model的问题
  • Red Hat Linux 全版本镜像下载
  • vm.nr_hugepages参数配置错误导致系统无法启动
  • 【Qt】Qt 设置全局字体
  • c++ cpp 多叉树简单处理文件重复包含问题
  • YOLO系列目标检测模型演进与YOLOv13深度解析
  • 【基础知识】仿函数与匿名函数对比
  • 澳鹏数据集月度精选 | 覆盖全模态理解、复杂推理、海量真题的快速部署方案
  • 2025年- H136-Lc191.位1的个数(位运算)--Java版
  • 第五节 JavaScript——引用类型、DOM/BOM 与异步编程
  • 基础算法之二分算法 --- 2
  • Vue3+JS 复杂表单实战:从验证到性能优化的全流程方案
  • 基于RAG的智能客服系统
  • 建自己的Python项目仓库,使用工具:GitHub(远程仓库)、GitHub Desktop(版本控制工具)、VSCode(代码编辑器)
  • 容器使用卷
  • Vue3:根据el-input封装全局v-focus指令
  • 企业AI战略构建与成品选择指南
  • Semaphore和CountDownLatch
  • 实战ELK与AI MCP:构建高可用的智能化日志可观测体系
  • SAP-MM:SAP MM学习分享:深入浅出解析物料需求计划(MRP)及MRP配置图解
  • 【LLM】使用 Google ADK、Gemini、QDrant 和 MCP 构建深度研究系统
  • 【CSS学习笔记2】-css复合选择器
  • 186. Java 模式匹配 - Java 21 新特性:Record Pattern(记录模式匹配)
  • Electron下载失败
  • Origin绘制双Y轴网格叠加图|科研论文图表教程(附数据排列格式)
  • XXL-JOB框架SRC高频漏洞分析总结
  • 未启用Spring事务管理 执行mapper.xml文件的sql,为什么会自动提交
  • 亚马逊云代理:亚马逊云怎么样进行大规模数据分析与处理?
  • Linux防火墙iptables
  • 基于联邦学习与神经架构搜索的可泛化重建:用于加速磁共振成像|文献速递-最新医学人工智能文献