【JavaEE初阶】多线程6(线程池\定时器)

news/2024/9/18 23:55:20 标签: java, 算法, 开发语言

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗~

如有错误,欢迎指出~



目录

 实例3:线程池

参数解释 

核心线程数, 最大线程数

允许空闲的最大时间 ,时间单位 

任务队列(阻塞队列) 

线程工厂=>工厂设计模式 

拒绝策略 

使用举例

模拟实现一个线程池(固定线程数目的线程池)

实例4:定时器

使用举例 

定时器的模拟实现

选定数据结构 

线程安全问题

不使用sleep 


 实例3:线程池

线程池 就是把线程提前从系统中申请好,放到一个地方,后面需要使用线程的时候,直接从这个地方来取,而不是从系统中重新申请,线程用完之后,也是会还回到刚才的地方.主要是解决随着业务上对于性能要求越来越高,线程创建开销的频次越来越多的 问题,

参数解释 

核心线程数, 最大线程数

允许空闲的最大时间 ,时间单位 

任务队列(阻塞队列) 

线程工厂=>工厂设计模式 

拒绝策略 

使用举例

ThreadPoolExecutor 是封装前的 ->定制性更强,用起来更麻烦

Executors 是封装过的->定制性比较弱,用起来简单 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo30 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService service= Executors.newFixedThreadPool(4);//代表线程池中有4个线程
        for (int i = 0; i < 100; i++) {
            int id =i;//让id成为事实final,让lambda捕获
            service.submit(()->{
               Thread current = Thread.currentThread();//当前线程
                System.out.println("hello thread"+ id +","+ current.getName());
            });
        }

        //最好不要立即就终止, 可能任务还没执行完,线程就终止了
        Thread.sleep(2000);

        //把线程池里的线程都 终止掉
        service.shutdown();
        System.out.println("程序退出");
    }
}

加上 service.shutdown(),让前台线程强制结束

如何指定线程池中的线程个数 

模拟实现一个线程池(固定线程数目的线程池)

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThreadPool{

    private BlockingQueue<Runnable> queue =new ArrayBlockingQueue<>(1000);
    //此处n表示创建几个线程
    public MyThreadPool(int n){
        //先创建n个线程
        for (int i = 0; i < n; i++) {
            Thread t =new Thread(()->{
                //循环的从队列中 取任务
                while(true){
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();//执行任务
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }

    //添加任务
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

public class Demo31_MyPool {
    public static void main(String[] args) throws InterruptedException {
        //测试代码
        MyThreadPool pool=new MyThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            int id=i;
            pool.submit(()->{
                System.out.println("执行任务"+ id +"," + Thread.currentThread().getName());
            });//lambda对应的是"函数式接口", Runnable 也是同样符合这样的要求的

        }
    }
}

实例4:定时器

定时器相当于"闹钟",网络通信中,经常需要设定一个"超时时间",Java标准库中也提供了定时器实现

定时器在后端开发中特别重要和常用,和"阻塞队列" 类似,也会有专门的服务器(用来在分布式系统中 实现定时器的效果)

如果定的任务时间都是一样的值,接下来任务的执行顺序可能是串行的,也可能是并发的(取决于定时器的具体实现)

使用举例 

TimerTask本质上就是Runnable的进一步实现

import java.util.Timer;
import java.util.TimerTask;

public class Demo31 {
    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        },3000);//表示延时3秒钟 打印hello3

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        },2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello1");
            }
        },1000);
        System.out.println("程序开始执行");
    }
}

定时器的模拟实现

定时器的实现步骤:

  1. 创建类,描述一个要执行的任务(任务的内容,任务的时间)
  2. 管理多个任务,通过一定的数据结构,把多个任务存起来
  3. 有专门的线程,执行这里面的任务
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;

class MyTimerTask implements Comparable<MyTimerTask> {
    private Runnable runnable;
    // 此处这里的 time, 通过毫秒时间戳, 表示这个任务具体啥时候执行.
    private long time;

    public MyTimerTask(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(MyTimerTask o) {
        // 此处这里的 - 的顺序, 就决定了这里是大堆还是小堆.
        // 此处需要小堆.
        // 这里是谁减谁, 不要背. 可以先写成一种顺序, 试试.
        return (int) (this.time - o.time);
        // return (int) (o.time - this.time);
    }
}

class MyTimer {
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object locker = new Object();

    public MyTimer() {
        // 创建线程, 负责执行上述队列中的内容
        Thread t = new Thread(() -> {
            try {
                while (true) {
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            locker.wait();
                        }
                        MyTimerTask current = queue.peek();
                        // 比如, 当前时间是 10:30, 任务时间是 12:00, 不应该执行.
                        // 如果当前时间是 10:30, 任务时间是 10:29, 应该执行
                        if (System.currentTimeMillis() >= current.getTime()) {
                            // 要执行任务
                            current.run();
                            // 把执行过的任务, 从队列中删除.
                            queue.poll();
                        } else {
                            // 先不执行任务
                            locker.wait(current.getTime() - System.currentTimeMillis());
                            // Thread.sleep(current.getTime() - System.currentTimeMillis());
                        }
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
    }

    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
            queue.offer(myTimerTask);
            locker.notify();
        }
    }
}

选定数据结构 

按照时间来执行任务,只要能够确定所有任务中时间最小的任务,判定其是否到达执行时间即可(其他时间的任务必定排在 时间最小任务后面),所以使用优先级队列是最好的选择

为啥不使用BlockingQueue阻塞队列作为实现定时器的数据结构?

  • 阻塞队列里的take里也有一把锁,容易出现死锁情况
  • 代码的复杂程度会增加

对于所有的修改操作都要加上锁

线程安全问题

通过对进队列和出队列进行加锁

使用wait操作,避免出现 "线程饿死" 这种情况

不使用sleep 

为啥使用wait,不使用sleep让线程阻塞?

业界实现定时器除了基于优先级队列的方式之外,还有一种典型的实现方式,"时间轮"(也是一种巧妙设计的数据结构)


http://www.niftyadmin.cn/n/5664732.html

相关文章

windows使用tcpdump.exe工具进行抓包教程

windows主机安装一些抓包工具可能有些不方便&#xff0c;这里有一个tcpdump.exe工具直接免安装&#xff0c;可以直接使用进行抓包。&#xff08;工具下载见 附件&#xff09; tcpdump.exe使用教程 如下&#xff1a; 1&#xff1a;tcpdump -D 可查看网络适配器(注意前面的编号)…

OpenHarmony(鸿蒙南向开发)——轻量和小型系统三方库移植指南(一)

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——轻量系统芯片移植指南(一) Op…

Web植物管理系统-下位机部分

本节主要展示上位机部分&#xff0c;采用BSP编程&#xff0c;不附带BSP中各个头文件的说明&#xff0c;仅仅是对main逻辑进行解释 main.c 上下位机通信 通过串口通信&#xff0c;有两位数据验证头&#xff08;verify数组中保存对应的数据头 0xAA55) 通信格式 上位发送11字节…

Python那些关于字符串的操作

Python那些关于字符串的操作 1 前言2 字符串的处理操作.2.1分割字符串2.2拼接字符串2.3正则表达式2.4enumerate2.5字符串中的大小写转化2.6 对齐加数 总结 1 前言 python关于字符串的操作很多&#xff0c;而正则化直接是打开新世界的大门。同一种说法&#xff0c;使用不同语言…

【Ubuntu】Ubuntu双网卡配置 实现内外网互不影响同时可用

【Ubuntu】Ubuntu双网卡配置 实现内外网互不影响同时可用 建议前提配置用到的命令参考文献&#xff1a; 建议 本文仅作个人记录&#xff0c;请勿完全照搬&#xff0c;建议直接看此视频&#xff0c;按作者的步骤进行配置 linux配置内外网&#xff08;ubuntu举例&#xff09;&am…

关于wordPress中的用户登录注册等问题

前言 大家在做类似的功能的时候&#xff0c;有没有相关的疑问。那就是我都已经选择好了相应的主题和模版&#xff0c;但是为什么都没有用户注册和用户登录的页面存在呢&#xff1f; WordPress默认情况下不提供用户注册和登录功能的原因是它最初是作为一个博客平台开发的&…

IDS Clearing House Core 项目入门

IDS Clearing House 核心由两个微服务组成&#xff1a;Document API 和 Keyring API。它们共同支持 Clearing House 服务&#xff0c;这是工业数据空间中 Clearing House 组件的一个原型实现。Clearing House 的主要功能是提供一个可以存储和检索数据的 API&#xff0c;所有的数…

【Linux】权限理解(超详细)

目录 用户 角色切换 创建和删除普通用户 权限管理 文件访问者分类&#xff08;人&#xff09; 文件类型和访问权限&#xff08;事物属性&#xff09;​编辑 文件访问权限的相关设置方法 chmod chown chgrp 问题解答 粘滞位 用户 Linux下有两种用户&#xff1a;超级…