并发编程第11篇,线程池的一些常用用法和使用

线程池源码解读

什么是线程池

线程池和数据库连接池非常类似,可以统一管理和维护线程,减少没有必要的开销。

 

为什么要使用线程池

因为频繁的开启线程或者停止,线程需要从新被cpu从就绪到运行状态调度,效率非常低。

所以使用线程可以实现复用,从而提高效率。

 

线程池的作用

 

1.降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。

2.提高响应速度:任务到达时,无需等待线程创建即可立即执行。

3.提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

4.提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

线程池的创建方式

 Executors.newCachedThreadPool(); 可缓存线程池

 Executors.newFixedThreadPool();可定长度

 Executors.newScheduledThreadPool() ; 可定时

 Executors.newSingleThreadExecutor(); 单例

底层都是基于ThreadPoolExecutor构造函数封装

简单手写线程池

阻塞队列

 

1.ArrayBlockingQueue:

有界队列,基于数组结构,按照队列FIFO原则对元素排序;

2.LinkedBlockingQueue:

无界队列,基于链表结构,按照队列FIFO原则对元素排序,Executors.newFixedThreadPool()使用了这个队列; 无界默认是Integer.MAX_VALUE,有界则是 可以自己定义

3.SynchronousQueue:

同步队列,该队列不存储元素,每个插入操作必须等待另一个线程调用移除操作,否则插入操作会一直被阻塞,Executors.newCachedThreadPool()使用了这个队列;

4.PriorityBlockingQueue:

优先级队列,具有优先级的无限阻塞队列。

 

 

线程池手写原理

一个核心线程任务集合,一个缓存线程队列,核心线程任务处于一直运行状态,核心线程里面的任务从缓存队列里面取,核心线程用完来不及处理的就放在缓存线程任务中排队等候处理,通过构造方法初始化核心线程集合大小和缓存队列大小

package com.example.demo.mall.threadPool;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class MyExecutor {
    /***
     * 存放核心线程数
     */
    private List<TaskThread> taskThreads;
    /**
     * 缓存队列
     */
    private BlockingQueue<Runnable> blockingQueues;

    public MyExecutor(int codeThreads, int threadQueues) {
        taskThreads = new ArrayList<TaskThread>(codeThreads);
        this.blockingQueues = new LinkedBlockingQueue<Runnable>(threadQueues);
        for (int i = 0; i < codeThreads; i++) {
            TaskThread taskThread = new TaskThread();
            taskThread.start();
            taskThreads.add(taskThread);
        }
    }

    public MyExecutor(int codeThreads, BlockingQueue<Runnable> blockingQueues) {
        taskThreads = new ArrayList<TaskThread>(codeThreads);
        this.blockingQueues = blockingQueues;
        for (int i = 0; i < codeThreads; i++) {
            TaskThread taskThread = new TaskThread();
            taskThread.start();
            taskThreads.add(taskThread);
        }
    }


    class TaskThread extends Thread {
        @Override
        public void run() {
            while (true) {
                Runnable runnable = blockingQueues.poll();
                if (runnable != null) {
                    runnable.run();
                }
            }
        }
    }

    public boolean executor(Runnable runnable) {
        return blockingQueues.offer(runnable);
    }

    public boolean executor2() {
        Runnable runnable = blockingQueues.poll();
        return blockingQueues.offer(runnable);
    }

    public static void main(String[] args) throws InterruptedException {
        MyExecutor myExecutor = new MyExecutor(2, 2);
        Thread t1 = new Thread(() -> {
            System.out.println("t1线程运行");
        });
        Thread t2 = new Thread(() -> {
            System.out.println("t2线程运行");
        });
        Thread t3 = new Thread(() -> {
            System.out.println("t3线程运行");
        });
        BlockingQueue<Runnable> queues = new LinkedBlockingQueue<Runnable>(3);
        queues.put(t1);
        queues.put(t2);
        queues.put(t3);
        myExecutor.executor(t1);
        MyExecutor myExecutor2 = new MyExecutor(2, queues);
        myExecutor2.executor2();
//        for (int i = 0; i < 10; i++) {
//            int finalI = i;
//            boolean executor = myExecutor.executor(() -> {
//                System.out.println(Thread.currentThread().getName() + "," + finalI);
//            });
//        }
    }



}

 

 

线程池原理分析

ThreadPoolExecutor核心参数

corePoolSize:核心线程数量  一直正在保持运行的线程

maximumPoolSize:最大线程数,线程池允许创建的最大线程数。

keepAliveTime:超出corePoolSize后创建的线程的存活时间。

unit:keepAliveTime的时间单位。

workQueue:任务队列,用于保存待执行的任务。

threadFactory:线程池内部创建线程所用的工厂。

handler:任务无法执行时的处理器。

 

线程池的状态

1.RUNNING:线程池能够接受新任务,以及对新添加的任务进行处理。

2.SHUTDOWN:线程池不可以接受新任务,但是可以对已添加的任务进行处理。

3.STOP:线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。

4.TIDYING:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行构造函数5.terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。

6.TERMINATED:线程池彻底终止的状态。

 

核心原理:

  1. 提交任务的时候比较核心线程数,如果当前任务数量小于核心线程数的情况下,则直接复用线程执行。
  2. 如果任务量大于核心线程数,则缓存到队列中。
  3. 如果缓存队列满了,且任务数小于最大线程数的情况下,则创建线程执行。
  4. 如果队列且最大线程数都满的情况下,则走拒绝策略。

注意:最大线程数,在一定时间没有执行任务 则销毁避免浪费cpu内存。

 

专业术语。

1.当线程数小于核心线程数时,创建线程。

2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。

3.当线程数大于等于核心线程数,且任务队列已满

3.1若线程数小于最大线程数,创建线程

3.2若线程数等于最大线程数,抛出异常,拒绝任务

 

自定义线程池

public class MyThreadPoolExecutor {

    public static ExecutorService newFixedThreadPool(int corePoolSize, int maximumPoolSize, int blockingQueue) {

        return new ThreadPoolExecutor(corePoolSize, maximumPoolSize,

                60L, TimeUnit.MILLISECONDS,

                new LinkedBlockingQueue<Runnable>(blockingQueue), new MayiktExecutionHandler());

    }

}

 

线程池常见问题

线程池队列拒绝策略

如果队列满了,且任务总数>最大线程数则当前线程走拒绝策略。

可以自定义异拒绝异常

rejectedExecutionHandler:任务拒绝处理器
两种情况会拒绝处理任务:
1.当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
2.当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。

线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认是AbortPolicy,会抛出异常。
ThreadPoolExecutor类有几个内部实现类来处理拒绝任务:
1.AbortPolicy 丢弃任务,抛运行时异常
2.CallerRunsPolicy 执行任务
3.DiscardPolicy 忽视,什么都不会发生
4.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
5.实现RejectedExecutionHandler接口,可自定义处理器

 

 

线程池参数配置多少最合理

具体可以根据cpu密集与io密集配置

CPU密集,线程不会阻塞,一直在运行,线程代码非常快结束,则最大线程数配置与

Cpu核数相当就可以了。

Io密集,比如读取io、导致当前线程有可能阻塞,或者线程执行代码非常耗时,则最大线程数配置为Cpu核数*2即可。

为什么阿里不建议使用Executors

阿里巴巴官方技术手册推荐最好使用

ThreadPoolExecutor自己定义

为什么需要开发者自己基于ThreadPoolExecutor构造函数封装?

答案:

  1. 因为底层都是基于无界队列实现, 缓存线程队列可以无限的存放容量大小,有可能会内存溢出。
  2. 线程池中最大线程数是无效的。
ThreadPoolExecutor构造函数有那些参数
1.corePoolSize 核心线程数 一直正在运行的线程
2.maximumPoolSize 最大线程数 
A.满足条件 队列满的情况下
最大线程数组成:核心线程数+非核心线程数=最大线程数
corePoolSize =10 maximumPoolSize 15
非核心线程数=15-10;
keepAliveTime 超时时间 60s 在一定时间内没有线程使用
非核心线程数的情况下,则销毁,节约cpu的资源。如果我们线程一直在运行状态存在那些缺陷:非常消耗cpu资源
  1. BlockingQueue<Runnable> workQueue  缓存线程队列。
  2. Handler  自定义封装拒绝策略回调方法
ThreadPoolExecutor底层原理解读
核心线程数10最大线程数15 只有缓存队列有界的最大线程数才有用,超出缓存队列就再开启一个线程开始消费,但开启的数目累计最多不能超过最大线程数 缓存队列无界默认 Integer最大值 2的32次方 
  1. 提交线程任务数(1-15)<核心线程数10  直接使用核心线程数复用。 任务的id1-10 直接使用核心线程数复用。
  2. 提交线程任务数(10-15)>核心线程数10 当前我们核心线程没有足够的线程运行的情况下,超过5任务id直接缓存到队列中。
  3. 如果队列没有满的情况下,都是我们核心线程数一直在复用。
  4. 如果队列满了的情况下,非核心线程数: 最大线程数-核心线程数=
    线程池同时最大容量:最大线程数+
    缓存队列
    拒绝策略:提交线程任务数>
    最大线程数+缓存队列 =拒绝策略。
    因为我们java中自己封装的线程:都是采用无界队列,
    队列如果没有满的情况下,非核心线程数不会开启,也不会走拒绝策略。
    这样的话有可能会内存溢出。
    最大线程数一定要比核心线程数:计算出非核心线程数
    最大线程数=核心线程数
    +最大线程数
    非核心线程数超时时间:规定时间内,如果没有线程任务使用
    非核心线程数的情况下,则直接会销毁掉,节约服务器的内存。
    线程池拒绝线程任务:>最大线程数
    +队列缓存容量
    采用异常捕获
    将拒绝的线程任务记录到日志表中后期做定时任务补偿。

重点:各个参数和思考用有界队列存放缓存线程任务,还有5重拒绝策略

每一行代码都有它的涵义,多问一句为什么;别怕,理清思路,一切代码都是数据的流动和转化,耐心一点,慢慢积累!一起加油!!!

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值