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

news/2024/7/7 19:23:50 标签: java-ee, java, 开发语言, 后端

在这里插入图片描述

🎏🎏🎏个人主页🎏🎏🎏
🎏🎏🎏JavaEE专栏🎏🎏🎏
🎏🎏🎏上一篇文章:多线程代码案例(1)🎏🎏🎏

文章目录

  • 1.线程池
    • 1.1概念
    • 1.2线程池如何提高效率
    • 1.3标准库线程池的参数分析
    • 1.4模拟实现一个线程池
      • 1.4.1一个固定数目的线程池
      • 1.4.2含有最大线程数的线程池
    • 1.5线程池优点
    • 2.定时器
    • 2.1概念
    • 2.2Java标准库中的定时器(Timer)
    • 2.3模拟实现定时器
      • 2.3.1定时器的需求
      • 2.3.2实现需求的技术
      • 2.3.3代码的实现
    • 1.4总结

1.线程池

1.1概念

将你需要用到的线程提前创建好,然后放到用户态通过数据结构的形式来管理。

1.2线程池如何提高效率

我们直接创建线程是内核态与用户态两一起配合完成的,如果频繁的去创建线程销毁线程,这样效率就会大大降低并且内核在完成一些任务是不可控的,面对这种情况我们就可以提前将我们需要用到的线程创建好放在用户态种通过数据结构管理起来,当需要使用的时候就通过用户态来调用,不要用就放回用户态中,减少内核态的参与相当于减少了不可控性那么效率就会提高。

1.3标准库线程池的参数分析

ThreadPoolExecutor
在这里插入图片描述

  1. int corePoolSize int maximumPoolSize
    corepoolSize(核心线程数)——根据CPU的逻辑核心数决定的
    maximumPoolSize(最大线程数)——由核心线程数+非核心线程数
    对线程池中的线程分为两种:核心线程和非核心线程
    核心线程:当线程池被创建了,核心线程也就有了。
    非核心线程:当任务过多的时候,核心线程处理不过来的时候,线程池就会临时创建线程来分担任务,当任务变轻的时候,那么这些非核心线程就会被回收,这样当任务繁重的时候,增加一些非核心线程就会提高效率,当任务空闲的时候就可以减少开销。
    那么最大的线程数应该设多少比较合适呢?
    关于设多少比较合适不仅仅和电脑的配置有关系还和代码类型有关系。
    代码类型在理想的情况下分为两种:CPU密集类型和IO密集类型
    CPU密集类型:
    代码中基本都是算术运算,条件判断,循环判断,函数调用这些都是需要大量调用CPU来参加工作的,那么这种最大的线程数应该要小于等于逻辑核心数。
    IO密集类型:
    IO类型的每一个线程消耗的CPU只有一点点,影响的主要是其他方面比如网卡带宽,硬盘访问…,那么最大线程数可以大于等于逻辑核心数。
    但我们生活中可不会有这种理想情况发生,一般都是CPU密集型于IO密集型结合的情况,那么这个最大的线程数怎么去确定呢,可以根据做实验的方式(控制变量法)来确定一个相对正确的数据。
  2. long keepAliveTime TimeUnit unit
    long keepAliveTime——允许非核心线程最大的空闲时间
    TimeUnit unit——空闲时间单位
    给定时间可以增加容错率,防止任务少的时候突然任务剧增,这样就可以给回收非核心线程缓冲时间。
  3. BlockingQueue workQueue
    BlockingQueue workQueue——线程池的任务队列
    线程池会提供submit方法,让其他线程将任务提交给线程池,此时的线程池就需要队列这样的数据结构,将任务管理起来,这个队列存储的元素其实就是Runnable对象,要执行的逻辑其实就是run方法中的内容
  4. ThreadFactory threadFactory
    ThreadFactory threadFactory——java标准库中提供的工厂类
    当我们需要创建一个点,有两种方式一种是笛卡尔坐标表示法,另一种是极坐标表示法
java">class Point {
    point(double x,double y) {
    
    }
    point(double r,double a) {

    }
}

此时的两种方式都是通过构造方法来创建的,但是这两种方法构成不了重载,所以无法实现,因为在某些特殊情况构造方法会带来一些麻烦,就出现了工厂方法来封装一些这些构造方法,这种模式叫工厂模式

java">class Point {
    public static point makePointByXY(double x, double y) {
        Point p = new Point();
        p.set(x);
        p.set(y);
        return p;
    }
    public static point makePointByRA(double r, double a) {
        Point p = new Point();
        p.set(r);
        p.set(a);
        return p;
    }
}

上述代码就是一种通过工厂方法来封装这些构造点的方法。
5. RejectedExecutionHandler handler
RejectedExecutionHandler handler——拒绝策略,是一种以枚举的方式表示的。

  1. AbortPolicy():超出负荷,直接抛出异常
  2. CallerRunsPolicy():调用者负责处理多出来的任务
  3. DiscardOldestPolicy():丢弃队列中最老的任务
  4. DiscardPolicy():丢弃新来的任务
    由于ThreadPoolExecutor用起来非常费劲,于是就提供了几个工厂类例如:Executor
    通过工厂类中提供的方法可以创建线程池的几种方式:
  5. Executors.newFixedThreadPool()——创建固定线程数目的线程池
  6. Executors.newSingleThreadExecutor()——创建一个只包含单个线程的线程池
  7. Executors.newScheduledThreadPool(4)——创建一个固定线程个数, 但是任务延时执行的线程池
    创建一个固定线程数的线程池:
java">public static void main(String[] args) {
    ExecutorService service = Executors.newFixedThreadPool(10);
    for (int i = 0; i <=50000 ; i++) {
        int id = i;
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello " + id + ", " + Thread.currentThread().getName());
            }
        });
    }
}

1.4模拟实现一个线程池

  1. 需要若干个线程
  2. 需要任务队列
  3. 需要submit方法

1.4.1一个固定数目的线程池

java">//创建一个简单的线程
public class MyThreadPollBasicEdition {
    //创建一个任务队列
    public BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
    //初始化线程池
    public MyThreadPollBasicEdition(int n) {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(()-> {
                    try {
                        while(true) {
                            Runnable runnable = queue.take();
                            runnable.run();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
            });
            t.start();
        }
    }
    //提供submit方法
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    public static void main(String[] args) throws InterruptedException {
        MyThreadPollBasicEdition myThreadPollBasicEdition = new MyThreadPollBasicEdition(10);
        for (int i = 0; i < 50000; i++) {
            int id = i;
            myThreadPollBasicEdition.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello " + id + ", " + Thread.currentThread().getName());
                }
            });
        }
    }
}

1.4.2含有最大线程数的线程池

java">ic class MyThreadPollAdvancedEdition {
    //创建一个任务队列的对象
    public BlockingQueue<Runnable> queue = new ArrayBlockingQueue(1000);
    //创建一个最大线程数
    public  int maxThreadSize = 0;
    //创建一个集合来存储若干个线程
    public List<Thread> threadList = new ArrayList<>();

    //创建构造方法
    public MyThreadPollAdvancedEdition(int corePoolSize, int maxThreadSize) {
       this.maxThreadSize = maxThreadSize;
        for (int i = 0; i < corePoolSize; i++) {
            Thread t = new Thread(()->{
                    try {
                        while (true) {
                            Runnable runnable = queue.take();
                            runnable.run();
                        }
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
            });
            t.start();
            threadList.add(t);
        }
    }
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
        //如果队列中的任务过多,导致线程不够用,可以增加一些线程
        if(queue.size() >=200 && threadList.size() < maxThreadSize) {
           Thread thread = new Thread(()-> {
               try {
                   while(true) {
                       Runnable runnable1 = queue.take();
                       runnable1.run();
                   }
               }
               catch (InterruptedException e) {
                   e.printStackTrace();
               }
           });
           thread.start();
           threadList.add(thread);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThreadPollAdvancedEdition myThreadPollAdvancedEdition = new MyThreadPollAdvancedEdition(10,20);
        for (int i = 0; i < 50000; i++) {
            int id = i;
            myThreadPollAdvancedEdition.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello " + id + ", " + Thread.currentThread().getName());
                }
            });
        }
    }
}

1.5线程池优点

  1. 降低资源消耗:减少线程的创建和销毁带来的性能开销。
  2. 提高响应速度:当任务来时可以直接使用,不用等待线程创建
  3. 可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。

2.定时器

2.1概念

用于实现定时操作、周期性任务和超时控制的作用

2.2Java标准库中的定时器(Timer)

Timer提供了一个schedule方法

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

2.3模拟实现定时器

2.3.1定时器的需求

  1. 能够延时执行任务和定时执行任务
  2. 能够管理多个任务

2.3.2实现需求的技术

  1. 需要一个描述任务和指定时间的类(本质就是一个Runnable)
  2. 需要一个数据结构来管理多个任务(优先级队列)
  3. 需要一个线程来扫描数据结构管理的任务

2.3.3代码的实现

java">//1.定义一个TimeTask类表示一个任务,这个类中需要任务执行的时间和描述任务。
class TimeTask implements Comparable<TimeTask> {
    public Runnable runnable;
    //此时的time不是程序等待的时间,而是一个绝对时间
    public long time;
    public TimeTask(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(TimeTask o) {
        return (int) (this.time - o.time);
    }
}
//2.定义一个数据结构来管理多个任务,此处我们优先级队列来管理多个任务,因为用其他的数据结构去管理的话,
// 就需要不断去扫描数据结构中满足要求的成员,遇到数据量庞大的那么开销就巨大,得不偿失,
// 用优先级队列可以避免这种情况,由于要求等待时间短的先运行,那么我们可以定义一个小根堆。
class MyTime{
    Object locker = new Object();
    public PriorityQueue<TimeTask> queue = new PriorityQueue<>();
    //初始化线程,并且调用任务
    public MyTime() {
        //定义一个扫描线程来获取堆顶任务
        Thread t = new Thread(()-> {
            try {
                while(true) {
                    synchronized (locker) {
                        if (queue.size() == 0) {
                            locker.wait();
                        }
                    //取小堆中堆顶的任务
                    TimeTask task = queue.peek();
                    //获取当前的时间戳
                    long curTime = System.currentTimeMillis();
                    //看任务的时间是否到了
                    if(curTime >= task.getTime()) {
                        //时间到了,则执行任务
                        task.run();
                        //任务执行完之后,则将队列中的堆顶任务消除
                        queue.poll();
                    } else {
                        //任务时间没到,则堵塞,阻塞多久?堵塞任务等待的时间
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
    }
    //提供一个schedule方法来创建任务
    public void schedule(Runnable runnable,long delay) {
        synchronized (locker) {
            TimeTask timeTask = new TimeTask(runnable,delay);
            queue.offer(timeTask);
            //唤醒调用任务的线程
            locker.notify();
        }
    }
}
public class MyTimerTest {
    public static void main(String[] args) {
        MyTime myTime = new MyTime();
        myTime.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,1000");
            }
        },1000);

        myTime.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,2000");
            }
        },2000);
        myTime.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,3000");
            }
        },3000);
    }
}

1.4总结

  1. 创建一个类,表示一个任务(Runnable 任务本体 time任务的执行时间)
  2. 引入数据结构来管理多个任务(用的是优先级队列,省去遍历的开销)
  3. 引入扫描线程,不停的循环获取队列队首任务,判定是否到时间,到时间就执行,并且出队列没到时间就阻塞。
  4. 引入锁,针对队列出和入的操作
  5. 解决忙等问题,引入wait和notify,队列为空wait(死等)队首任务没到时间wait(带有超时时间)这里不要用sleep(sleep通过interrupt唤醒是非常规手段,sleep不会释放锁,会影响后续插入任务)
  6. 引入比较规则,让TimeTask可以按照时间先后来制定优先级。

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

相关文章

Python酷库之旅-第三方库Pandas(001)

目录 一、Pandas库的由来 1、背景与起源 1-1、开发背景 1-2、起源时间 2、名称由来 3、发展历程 4、功能与特点 4-1、数据结构 4-2、数据处理能力 5、影响与地位 5-1、数据分析“三剑客”之一 5-2、社区支持 二、Pandas库的应用场景 1、数据分析 2、数据清洗 3…

【Android面试八股文】Looper如何在子线程中创建?

文章目录 一、Looper的几个重要方法二、子线程中使用Looper的方式1三、子线程中使用Looper的方式23.1 使用HandlerThread实现3.2 HandlerThread源码解析创建子线程的 Looper必须要通过 Looper.prepare()初始化looper,然后再通过 Looper.loop()方法让 Loop运行起来。 那么具…

在Windows 11上更新应用程序的几种方法,总有一种适合你

序言 让你安装的应用程序保持最新是很重要的,而Windows 11使更新Microsoft应用商店和非Microsoft应用商店的应用程序变得非常容易。我们将向你展示如何使用图形方法以及命令行方法来更新你的应用程序。 如何更新Microsoft Store应用程序 如果你的一个或多个应用程序是从Mic…

SketchUp + Enscape+ HTC Focus3 VR

1. 硬件: 设备连接 2. 软件: 安装steam steamVR Vive Business streaming 3. 操作: 双方登录steam 账号,然后带上头盔,用手柄在HTC Focus3 安装 串流软件,选择串流软件,在Enscape中选择 VR 模式即可 4.最终效果: SketchUp Enscape HTC Focus 3 VR 实时预览_哔哩哔哩_bi…

责任链模式在金融业务中的应用及其框架实现

引言 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为设计模式&#xff0c;它通过为请求创建一个处理链&#xff0c;使多个对象都有机会处理这个请求。责任链模式通过将请求的发送者和接收者解耦&#xff0c;使得多个接收者可以依次处理请求&am…

[ruby on rails]rails6.0升级6.1

1.准备工作 在开始升级过程之前&#xff0c;我们有一些建议的准备工作。 升级的时候&#xff0c;最好一个版本一个版本升级&#xff0c;比如6.0到6.1再到7.0&#xff0c;不要一次从6.0到7.0至少80%的测试覆盖率&#xff0c;测试真的很重要&#xff0c;能确保升级快速完成。本…

论文解读StyleGAN系列——StyleGANv1

论文&#xff1a;A Style-Based Generator Architecture for Generative Adversarial Networks&#xff08;2018.12&#xff09; 作者&#xff1a;Tero Karras, Samuli Laine, Timo Aila 链接&#xff1a;https://arxiv.org/abs/1812.04948 代码&#xff1a;https://github.com…

【Android面试八股文】Android性能优化面试题:怎样检测函数执行是否卡顿?

文章目录 卡顿一、可重现的卡顿二、不可重现的卡顿第一种方案: 基于 Looper 的监控方法第二种方案:基于 Choreographer 的监控方法第三种方案:字节码插桩方式第四种方案: 使用 JVMTI 监听函数进入与退出总结相关大厂的方案ArgusAPMBlockCanaryQQ空间卡慢组件Matrix微信广研参…