多线程案例【二】

2023-11-09

定时器

定时器像是一个闹钟,在一定时间之后,被唤醒并执行某个之前设定好的任务。

之前学习的 join(指定超时时间)
sleep(休眠指定时间)
都是基于系统内部的定时器,来实现的。

标准库中的定时器

标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule 。
schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)

public class Demo23 {
    public static void main(String[] args) {
        Timer timer =new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello timer");
            }
        },3000);
        System.out.println("main");
    }
}

执行效果
在这里插入图片描述

实现定时器

Timer内部需要什么东西?

1)描述任务
创建一个专门的类来表示一个定时器中的任务(TimerTask)


//创建一个类,表示一个任务
class MyTask{
    //任务具体要干什么
    private Runnable runnable;
    //任务具体什么时候干,保存任务要执行的毫秒级时间戳
    private long time;

    public MyTask(Runnable runnable, long after) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis()+after;
    }
    public  void run(){
        runnable.run();

    }

}

2)组织任务(使用一定的数据结构把一些任务给放到一起)
在这里插入图片描述

class MyTimer{
    //定时器内部要能存放多个任务
    private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
    public void schedule(Runnable runnable,long delay){
        MyTask task=new MyTask(runnable, delay);
        queue.put(task);
    }
}

3)执行时间到了的任务
需要先执行时间在靠前的任务
就需要一个线程,不停的去检查当前优先级队列的队首元素,看看说当前最靠前的这个任务是不是时间到了。

//提供一个MyTimer的构造方法
    public MyTimer(){
        Thread t=new Thread(() ->{
            while(true){
                try {
                    //先取出队首元素
                    MyTask task=queue.take();
                    //在比较一下看看当前这个任务时间到了没
                    long curTime=System.currentTimeMillis();
                    if(curTime < task.getTime()){
                        //时间没到,把任务在放到队列中
                        queue.put(task);
                    }else{
                        //时间到了,执行任务
                        task.run();

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        t.start();
    }

上述代码存在严重缺陷!!!
第一个缺陷:MyTask没有指定比较规则在这里插入图片描述
MyTask这个类的比较规则,并不是默认就存在的,这个需要手动指定,按照时间大小来比较的。
Java标准库中的集合类,很多都有一定的约束限制的,不是随便拿个类都能放到这些集合类里面去的

第二个缺陷
在这里插入图片描述
可以基于wait这样的机制来实现
wait有个版本,指定等待时间(不需要notify,时间到了自然唤醒)。
计算出当前时间和任务目标之间 的时间差,就等待这么长时间。

那么既然是指定一个等待时间,,为什么不直接使用sleep呢?而是要用wait?
sleep不能被中途唤醒
wait能够被中途唤醒
在等待过程中,可能要插入新的任务,新的任务是可能出现在之前所有任务的最前面的,在schedule操作中,就需要加上一个notify操作。

因此最终的代码是:

//创建一个类,表示一个任务
class MyTask implements Comparable<MyTask>{
    //任务具体要干什么
    private Runnable runnable;
    //任务具体什么时候干,保存任务要执行的毫秒级时间戳
    private long time;

    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis()+delay;
    }
    public  void run(){
        runnable.run();

    }

    public long getTime(){
        return time;
    }

    @Override
    public int compareTo(MyTask o) {
        return (int) (this.time-o.time);
    }
}

class MyTimer{
    //定时器内部要能存放多个任务
    private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
    public void schedule(Runnable runnable,long delay){
        MyTask task=new MyTask(runnable, delay);
        queue.put(task);
        //每次任务插入成功之后,都唤醒一下扫描线程,让线程重新检查一下队首元素的任务是否时间到要执行
        synchronized (locker){
            locker.notify();
        }
    }

      private Object locker=new Object();

    //提供一个MyTimer的构造方法
    public MyTimer(){
        Thread t=new Thread(() ->{
            while(true){
                try {
                    //先取出队首元素
                    MyTask task=queue.take();
                    //在比较一下看看当前这个任务时间到了没
                    long curTime=System.currentTimeMillis();
                    if(curTime < task.getTime()){
                        //时间没到,把任务在放到队列中
                        //指定一个等待时间
                        synchronized (locker){
                            locker.wait(task.getTime()-curTime);
                        }
                        queue.put(task);
                    }else{
                        //时间到了,执行任务
                        task.run();

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        t.start();
    }
}

public class Demo24 {
    public static void main(String[] args) {
        MyTimer timer=new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello timer");
            }
        },3000);
        System.out.println("hello main");

    }
}

线程池

进程,比较重,频繁创建销毁,开销大,解决方案:进程池或线程。
线程,虽然比进程轻了,但是如果创建销毁的频率进一步增加,仍然会发现开销还是有的,解决方案:线程池或协程。

把线程提前创建好,放到池子里,后面需要使用线程,直接从池子里取,就不必从系统这边申请了。线程用完,也不是还给系统,而是放回池子里,以备下次再用。
这回创建销毁过程,速度就更快了。

为什么认为线程放到池子里,就比从系统这边申请释放更快呢??
在这里插入图片描述

Java标准库的线程池

在这里插入图片描述

有一个程序,这个程序要并发的 / 多线程的来完成一些任务,如果使用线程池的话,这里的线程数设为多少合适?
正确的做法:要通过性能测试的方式,找到合适的值
例如:写一个服务器程序,服务器里通过线程池,多线程的处理用户请求。
就可以对这个服务器进行性能测试,比如构造一些请求,发送给服务器,要测试性能,这里的请求就需要构造很多,比如每秒发送500 / 1000/2000…,根据实际的业务场景,构造一个合适的值。
根据这里不同的线程池的线程数,来观察,程序处理任务的速度,程序持有的CPU的占用率。
当线程数多了,整体的速度会快,CPU占用率也会高
当线程数少了,整体的速度会慢,CPU占用率也会下降。
需要找到一个让程序速度能接受,并且CPU占用率也合理这样的平衡点。
不同类型的程序,因为单个任务,里面的CPU上计算的时间和阻塞的时间分布是不同的。因此,只说一个数字是不靠谱的。

搞多线程,就是为了让程序跑的更快,那为什么考虑不让CPU占用率太高?
对于线上服务器来说,要留有一定的冗余,随时应对一些可能的突发情况(例如,请求突然暴涨)
如果本身已经把CPU快占完了,这时候突然来一波请求的峰值,此时服务器可能直接就挂了。

标准库中还提供了一个简化版本的线程池
Executors
本质是针对ThreadPollExecutor进行封装,提供了一些默认参数。

public class Demo25 {
    public static void main(String[] args) {
        //创建一个固定线程数目的线程池,参数指定了线程个数
        ExecutorService pool=Executors.newFixedThreadPool(10);
        //创建一个自动扩容的线程池,会根据任务自动进行扩容
        //Executors.newCachedThreadPool();
        //创建一个只有一个线程的线程池
       // Executors.newSingleThreadExecutor();
        //创建一个带有定时器功能的线程池,类似于Timer
       // Executors.newScheduledThreadPool();
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello threadpool");
            }
        });
    }
}

实现线程池

线程池中有什么?
1.先能够描述任务(直接使用Runnable)
2.需要组织任务(直接使用BlockingQueue)
3.能够描述工作线程
4.还需要组织这些线程
5.需要往线程池里添加任务

class MyThreadPool{
    //1.描述一个任务,直接使用Runnable,不需要额外创建类了
    //2.使用一个数据结构来组织若干个任务
    private BlockingDeque<Runnable> queue=new LinkedBlockingDeque<>();
    //3.描述一个线程,工作线程的功能就是从任务队列中取任务并执行
    static class Worker  extends  Thread{
        //当前线程池中有若干个worker线程,这些线程内部,都持有了上述的任务队列
        private  BlockingDeque<Runnable> queue=null;
        public Worker(BlockingDeque<Runnable> queue){
            this.queue=queue;

        }
        @Override
        public void run() {
            while(true){
                //就需要能够拿到上面的队列
                try {
                    //循环的获取任务队列中的任务
                    //这里如果队列为空,就直接阻塞,如果队列不为空,就获取到里面的内容
                    Runnable runnable=queue.take();
                    //获取到之后,就执行任务
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //创建哪一个数据结构来组织若干个线程
    private List<Thread> workers=new ArrayList<>();
    public MyThreadPool(int n){
        //在构造方法中,创建若干个线程,放到上述的数组中
        for(int i=0;i<n;i++){
            Worker worker=new Worker(queue);
            worker.start();
            workers.add(worker);
        }

    }
    //创建一个方法,能够让程序员来放任务到线程池
    public void submit(Runnable runnable){
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Demo26 {
    public static void main(String[] args) {
        MyThreadPool pool=new MyThreadPool(10);
        for(int i=0;i<100;i++){
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello thraedpool");
                }
            });
        }
    }

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多线程案例【二】 的相关文章

  • maven学习总结

    众所周知 maven的两大作用是项目构建和依赖管理 除此之外 基于多模块项目 maven常用的功能还有模块化管理 项目构建 Maven是一个构建工具 可以根据项目中的配置文件 pom xml 来自动执行项目的构建过程 它可以将源代码编译 运
  • Java-String类的常用方法

    Java String类的常用方法 1 常用方法1 int length 返回字符串的长度 return value length char charAt int index 返回某索引处的字符return value index bool
  • 【SpringBoot】简介及传统的 Spring 框架:对比和分析

    哈喽 哈喽 大家好 我是你们的老朋友 保护小周 今天给大家带来的是 SpringBoot 的简介 SpringBoot 项目的创建 相较于 Spring 框架的优点 1 快速的集成框架 2 内置运行容器 快速的部署项目 3 摒弃繁琐的 xm
  • 零基础自学计算机方法大全

    欢迎入读 尚学堂给同学们带来全新的Java300集课程啦 java零基础小白自学Java必备优质教程 学习从来没有捷径 只有学成之后才会一切是那么简单 想要学会编程 一定要有坚定的信念 1 选方向 定目标 首先你需要做好功课 了解计算机的分
  • JAVA多人聊天室(多线程基础聊天室可以私聊/群聊)

    创建一个类定义聊天的规则 package com test tcpmap 该程序定义了聊天的规则 public interface CrazyitProtocol 定义协议字符串的长度 int PROTOCOL LEN 2 下面是一些协议字
  • 【数据库】JDBC编程

    前言 小亭子正在努力的学习编程 接下来将开启javaEE的学习 分享的文章都是学习的笔记和感悟 如有不妥之处希望大佬们批评指正 同时如果本文对你有帮助的话 烦请点赞关注支持一波 感激不尽 目录 前言 什么是JDBC JDBC工作原理 JDB
  • File类的知识

    File 文章目录 File 概述 构造方法 抽象路径 成员方法 创建 删除 判断和获取和遍历 判断 获取 遍历 概述 java编写的一个专门用于描述计算机中的文件和文件夹的类 1 是文件和目录路径名的抽象表示 2 文件和目录是可以通过Fi
  • JVM知识点

    JVM知识点 概念 java内存模型 线程私有 内存溢出和内存泄漏 线程共享区域 存在GC 类加载 类加载机制 双亲委派模型 垃圾回收GC 概念 如何判断一个对象是垃圾 有两种算法 1 引用计数算法 2 可达性分析算法 JVM采取 垃圾回收
  • HmacMd5加密算法

    package com haiyisoft evportal token action import java security MessageDigest import java security NoSuchAlgorithmExcep
  • java基础知识

    java基础知识 java是一门编程语言 面向对象 是一门开源的计算机语言 曾用名Oak java市场需求 市面比较广 面向对象 可以开发的方面比较多 主要是是应用方面 android app 软件工具 微信小程序 大数据分析 当今社会上手
  • 在centos7中安装docker

    一 前置条件 64 bit 系统 kernel 3 10 CentOS 7 1 检查 使用 uname r 检查 kernel 版本 uname r 3 10 0 327 el7 x86 64 二 安装 yum安装方式 1 使用 sudo
  • EL表达式向select中赋值

    在使用el表达式的时候 有时需要向select下拉菜单中赋值 可以使用三目运算法进行赋值
  • 技术英雄会【新闻】CSDN最有价值博客TOP10颁奖【图】【我在左边数第四个】

    2007年04月06日 10 04 新浪科技夹带些私货 呵呵 社区英雄会 一 问周鸿祎一个问题 社区英雄会 二 问CSDN一个信息过滤器的问题 技术英雄会 三 社区英雄们的与会感言大赏 技术英雄会 四 也谈如何发掘到需要的内容和英雄 图为
  • 利用Java EE相关技术实现一个简单的Web聊天室系统

    利用Java EE相关技术实现一个简单的Web聊天室系统 1 编写一个登录页面 登录信息中有用户名和密码 分别用两个按钮来提交和重置登录信息 2 通过请求指派来处理用户提交的登录信息 如果用户名为本小组成员的名字且密码为对应的学号时 跳转到
  • XXX--1.0-SNAPSHOT.jar中没有主清单属性

    一 情况 将项目打包后 启动项目时报 yiqi 1 0 SNAPSHOT jar中没有主清单属性 二 原因 maven项目打包时没有配置主类 缺少plugin配置 三 解决 加上plugin配置
  • @Resource注解是什么作用,和@bean区别是什么?

    Resource 注解就像是 Java 开发的快递小哥 专门用来送依赖关系到你的代码门口 它的主要工作就是帮你实现依赖注入 把其他组件 比如类 对象 bean 啥的 送到你需要的地方 具体来说 依赖注入 Resource 负责把其他组件注入
  • JUC的常见类

    目录 Callable ReentrantLock Semaphore CountDownLatch JUC 即 java util concurrent 其中存放了一些进行多线程编程时有用的类 Callable Callable是一个接口
  • 线程安全的集合类

    Java中提供了许多集合类 其中有的是线程安全的 有的是线程不安全的 线程安全的集合类有 1 Vector Vector类实现了一个 动态数组 与ArrayList相似 但Vector是同步访问的 2 Stack Stack是Vector的
  • 【计算机毕业设计】二手家电管理平台

    时代在飞速进步 每个行业都在努力发展现在先进技术 通过这些先进的技术来提高自己的水平和优势 二手家电管理平台当然不能排除在外 二手家电管理平台是在实际应用和软件工程的开发原理之上 运用java语言以及前台VUE框架 后台SpringBoot
  • 【计算机毕业设计】宝鸡文理学院学生成绩动态追踪系统

    研究开发宝鸡文理学院学生成绩动态追踪系统的目的是让使用者可以更方便的将人 设备和场景更立体的连接在一起 能让用户以更科幻的方式使用产品 体验高科技时代带给人们的方便 同时也能让用户体会到与以往常规产品不同的体验风格 与安卓 iOS相比较起来

随机推荐

  • java中两个list对象取交集、差集

    在一般操作中 对于list集合取交集 差集 并集 比较简单 网上有很多例子 如 今天我们来说一下对于两个list集合该如何取交集与并集 如下两个集合 groupEntityList saveEntities groupEntityList是
  • JS正则判断多个连续相同字符

    var reg1 w 1 1 g 判断2个连续字符 var reg2 w 1 2 g 判断3个连续字符 var reg3 w 1 1 g 判断3个连续字符 var str aa 123AaAaAAA3 str match reg1 输出 a
  • SpringBoot学习笔记(一):先跑懂再说

    一 Spring Boot 入门 1 Spring Boot 简介 简化Spring应用开发的一个框架 整个Spring技术栈的一个大整合 J2EE开发的一站式解决方案 2 Spring Boot HelloWorld 一个功能 浏览器发送
  • 怎么样才能开期权账户

    为了保护投资者权益 上交所设定了50万的准入门槛 挡着了很多想入手期权交易的小伙伴 如果资金不够50万 那么有什么办法能零门槛参与期权呢 下文给大家介绍怎么样才能开期权账户的知识点 本文来自 期权酱 一 期权开户要什么条件 1 申请开户时保
  • android webview setwebviewclient,android – setWebViewClient和setWebChromeClient之间有什么区别?...

    从 source code Instance of WebViewClient that is the client callback private volatile WebViewClient mWebViewClient Instan
  • OpenCSV web下载csv文件demo

    OpenCSV web下载csv文件demo pom xml
  • 嵌入式Linux&Android开发-LCD屏幕调试

    目录 一 简介 二 开发流程 三 硬件说明 四 电子特性 五 关注启动时序 六 关注引脚 七 屏参适配 7 1 DTS 驱动配置 7 2 屏参配置 案例一 7 3屏参配置 案例二 7 4 屏参配置 案例三 7 5 屏参配置 案例四 7 6
  • 单元测试、集成测试、系统测试、验收测试

    本文是按照开发阶段划分测试技术 单元测试 单元测试是对软件组成单元进行测试 目的是检验软件基本组成单元的正确性 测试对象是软件设计的最小单位 模块 又称为模块测试 单元测试的实质是代码测代码 测试阶段 编码后或者编码前 TDD 编码前属于测
  • 树莓派笔记4:树莓派游戏机

    这次记录比较轻松的内容 将树莓派做成 游戏主机 当然这个主机只是具备模拟器功能而已 可以模拟街机 FC等平台上的游戏 最早要在树莓派上玩模拟器游戏需要手动安装和配置不同的模拟器 而现在国外很多爱好者专门制作了定制化的系统 直接把系统烧到树莓
  • latex插图\begin{minipage}强制左移\hspace命令

    事情是这样的 我在latex中插图 上面一张图是排列整整齐齐的图片 下面一张图就是我绘制的概率密度图 在使用latex插图的时候 因为概率密度图的纵坐标是有title的 所以会显得不整齐 如下图所示 在includegraphics前面添加
  • Inkscape 捕捉图标翻译

  • Docker Portainer 安装与报错处理

    安装docker 管理器 Portainer 最近在看spring cloud alibaba的时候 觉得docker是肯定要用的 然后找了个管理的docker的东东 比较方便的查询docker的情况 直接看操作吧 root localho
  • 分布式锁之redis实现

    docker安装redis 拉取镜像 docker pull redis 6 2 6 查看镜像 启动容器并挂载目录 需要挂在的data和redis conf自行创建即可 docker run restart always d v usr l
  • python字符串的常用方法(3-2)

    目录 一 字符串find 和index 获取某个值的位置方法 二 字符串strip lstrip rstrip左右去空格方法 三 字符串的replace 替换方法 四 字符串bool集合 一 字符串find 和index 获取某个值的位置方
  • vue项目通过directives指令实现vue实现盒子的移动;vue拖拽盒子;vue移动;

    vue项目 点击拖拽盒子 移动盒子 代码可直接复制 注意需要在移动的盒子上添加 v 指令 注意采用固定定位
  • 轻量级调试器神器 - mimikatz

    昨天有朋友发了个法国佬写的神器叫 mimikatz 让我们看下 神器下载地址 mimikatz trunk zip 还有一篇用这个神器直接从 lsass exe 里获取windows处于active状态账号明文密码的文章 http pent
  • 网络与信息安全应急处置预案

    分享一下我老师大神的人工智能教程 零基础 通俗易懂 http blog csdn net jiangjunshow 也欢迎大家转载本篇文章 分享知识 造福人民 实现我们中华民族伟大复兴 为加强北海市电子政务系统的安全 管理 形成科学有效 反
  • jpa自增id(@GeneratedValue和@GenericGenerator)

    一 JPA通用策略生成器 通过annotation来映射hibernate实体的 基于annotation的hibernate主键标识为 Id 其生成规则由 GeneratedValue设定的 这里的 id和 GeneratedValue都
  • Qt应用程序嵌入浏览器的常用方法

    1 使用QAxObject嵌入微软ActiveX软件 使用QAxObject需要包含Qt模块 QT axcontainer 注 1 此方式只针对微软的组件才有效 不可以用来加载第三方的应用程序 2 获取该组件的相关的API接口文档可以采用以
  • 多线程案例【二】

    目录 定时器 标准库中的定时器 实现定时器 线程池 Java标准库的线程池 实现线程池 定时器 定时器像是一个闹钟 在一定时间之后 被唤醒并执行某个之前设定好的任务 之前学习的 join 指定超时时间 sleep 休眠指定时间 都是基于系统