• Java EE——线程池


    线程池

    池的概念

    在之前的章节中我们就提到过池,例如字符串常量池,数据库连接池。
    池是为了提升我们代码的效率的。由于有些东西我们频繁的创建销毁过于消耗资源,因此我们用池将暂时用不到的资源存储起来,等到以后有需要了再从池中拿出来

    例如我们在招聘时被通知进入了公司的人才池,这并不说明你是个人才,而是说明公司嫌审核简历太麻烦了,现在还用不到你,等公司实在招不到人了才会调用这个人才池

    而我们的线程池虽然相对进程来说已经轻量化了,但是多次创建和销毁仍是一种低效的操作,因此有了线程池。在线程使用完成后放到线程池中,等到需要新的线程了再从池子中取出来

    为什么从池子中取比创建新的效率高

    这是因为我们的计算机由多个部分组成:硬件,驱动,内核,系统调用,应用程序。而应用程序就属于用户态,内核属于内核态。
    当我们创建线程时,就需要创建一个PCB,其本质是一个内核中的数据结构,因此我们就需要从用户态切换到内核态来创建
    而当我们从线程池中拿一个线程时,这是用户态自己就可以实现的,因此效率和开销更小

    Java标准库中的线程池

    首先创建一个池对象

    ExecutorService pool = Executors.newCachedThreadPool();
    
    • 1

    我们的这个写法不是构造方法,而是因为构造方法中的参数太多,进行优化后的一种写法——工厂方法

    工厂方法

    工厂方法是为了弥补构造方法的不足而产生的,由于构造实例时我们可能要传入多个参数,因此就要写多个构造方法,但是这几个构造方法可能参数类型和个数都一样,不构成重载,因此就会出现语法错误。因此我们用新的方法将这些构造方法封装起来,就可以创建不同的对象了

    demo
    public class Factory {
        static class Point{
            private double r;
            private double a;
            private double x;
            private double y;
    
            public static Point makePointByXY(double x, double y){
                Point p = new Point();
                p.setX(x);
                p.setY(y);
                return p;
            }
    
            private void setY(double y) {
                this.y = y;
            }
    
            private void setX(double x) {
                this.x = x;
            }
    
            public static Point makePointByRA(double r, double a){
                Point p = new Point();
                p.setR(r);
                p.setA(a);
                return p;
            }
    
            private void setA(double a) {
                this.a = a;
            }
    
            private void setR(double r) {
                this.r = r;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    创建完池任务后就可以加入任务,让线程池中的线程来完成这些任务

    pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务");
                }
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    其他参数

    标准库中的ThreadPool有一系列参数,让我们可以构造出不同的线程池

    corePoolSize和maximumPoolSize

    前者代表核心线程数,也就是线程池中的主力,就算空闲了也留在线程池中,后者是最大线程池,也就是主力加上替补

    当我们任务过多时,就会让主力和替补都上
    当我们没多少任务,这时就可以开除几个替补了

    keepAliveTime和unit

    也就是如果我们的替补多长时间不上场,就可以把他开除了,unit是时间的单位

    workQueue

    我们在创建线程池之前有可能自己就有一个队列,因此我们可以通过这个参数来为线程池传入自己的队列,如果不传入,线程池会自己创建一个队列

    threadFactory

    这个参数描述了线程如何创建,通过这个参数可以指定线程的创建的方法

    RejectedExecutionHandler handler

    由于我们线程池中的线程个数有限,超出数量就要阻塞等待,因此如果有过多的任务要完成,就可以通过这个参数来实现拒绝任务的策略

    1. ThreadPoolExecutor.AbortPolicy 代表如果任务太多了线程就崩溃了,神恶魔任务都不干了
    2. ThreadPoolExecutor.CallerRunsPolicy 代表如果任务太多了,就把任务还给给他任务的线程让他自己干
    3. ThreadPoolExecutor.DiscardOldestPolicy 代表如果任务太多了,就把一些最先安排的任务舍弃
    4. ThreadPoolExecutor.DiscardPolicy 代表如果任务太多了,就不干最新安排的任务

    demo

    创建一个核心线程数为5,最大线程数为10,任务队列为100,3秒的空闲开除替补,拒绝策略为忽略最新任务的线程池

    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.RejectedExecutionHandler;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class demo3 {
        public static void main(String[] args) {
            ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,10,3,
                    TimeUnit.SECONDS,new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.DiscardOldestPolicy());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    MyThreadPool

    我们要实现一个将用户发给我们的X个任务,分配给我们开发好的Y个线程。要做到当线程不够用时就让任务阻塞等待,当任务没了就让线程池阻塞等待
    因此我们可以用阻塞队列那节的消费者生产者模型来解决这个问题

    static class MyThreadPool{
            private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
            public void submit(Runnable runnable) throws InterruptedException {
                queue.put(runnable);
            }
            public MyThreadPool(int m){
                for (int i = 0; i < m; i++) {
                    Thread t = new Thread(() -> {
                        while(true){
                            Runnable runnable = null;
                            try {
                                runnable = queue.take();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            runnable.run();
                        }
                    });
                    t.start();
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我们的MyThreadPool中有一个阻塞队列参数,
    实现了submit方法——将传入的任务放到队列中。
    在构造方法中,我们传入了一个数量值,代表要线程池中的线程个数,然后用一个for循环将这些线程创建出来
    在每个线程中,都有一个循环的while,使之持续扫描阻塞队列中是否有新的任务需要完成

    线程池自定义线程个数

    我们自己定义的线程池线程个数究竟多少合适???
    这个问题并没有一个确切的答案,因为这和cpu的性能,任务的执行特点都是有关联的

    例如如果是CPU密集型任务,也就是有大量的算术运算和逻辑判断的任务,就会大量消耗cpu资源,也就应该少安排一些任务
    如果是IO密集型任务,也就是有很多读写任务,那么线程多了也没关系,对cpu的消耗没有那么大

    因此,我们应该通过实验的方式来确定多少线程数合适,通过设定不同的数目,测定程序的性能

  • 相关阅读:
    一对多关系实现部门—>员工的查询
    3dsMax---期末设计[CC‘s 游乐园’]
    计算机毕业设计Java网上鲜花交易平台(源码+系统+mysql数据库+Lw文档)
    Windows下Apache2.4配置SSL(HTTPS)
    MySQL 进阶 怎么去了解MySQL的架构原理
    直流有刷电机开环调速基于STM32F302R8+X-NUCLEO-IHM07M1(一)
    用户视角的比特币和以太坊外围技术整理
    R语言ggplot2可视化:gganimate包基于transition_time函数创建动态散点图动画(gif)
    【机器学习】SVM入门-硬间隔模型
    【云原生】springcloud08——Ribbon负载均衡调用
  • 原文地址:https://blog.csdn.net/m0_60867520/article/details/126936374