从线程池模型理解线程池的工作原理

本文深入解析线程池的工作原理,包括关键参数、模型及常见类型如CachedThreadPool、FixedThreadPool和SingleThreadExecutor的特性。探讨了线程池与内存管理的关系,提供了优化建议。
摘要由CSDN通过智能技术生成

首先要知道线程池是用来干嘛的,线程池是通过复用线程达到最大利用资源的

线程池的关键参数

线程池的构造方法如下

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

简单解释一下:
corePoolSize : 核心线程数,默认情况下不会退出;
maximumPoolSize: 最大线程数,这个数字是包括核心线程数的,最大线程数 = 核心线程数+非核心线程数;
long keepAliveTime:线程保持存活的时间,核心线程也可以设置,但是默认情况下不会退出;非核心线程数这个参数默认生效;
TimeUnit unit :keepAliveTime 的时间单位,时分秒等;
workQueue : 工作队列,是个阻塞队列;
threadFactory : 线程工厂,通常用来设置线程的名字和优先级等;
handler :当线程池处理不了提交的任务时,需要告诉线程池要怎么处理,例如抛异常等。

线程池模型

网上有很多讲解线程池的图片、代码讲解,但是看起来依然很费力,我把源码大体线程复用流程写了个伪代码(核心线程不停的默认情况),可以对比源码理思路,这里不贴了:

void execute(Runnable r){
    if(当前线程数<core ){
        New Thread(new Runnable(){
                Public void run(){
                       While(r!=null || r = takeFromQueue()!= null){
                                r.run(); 
                                r = null;  
                        }
                  }
            })
}else if(!queue.isFull()){
    queque.addTask(r);
}else if(当前线程数<max ){
        New Thread(new Runnbale(){
            Public void run(){
                    While(r != null || r = pollAllowTimeout(超时时间)){
                            r.run();
                            R = null;
                    }
            }
         })
    }else{
            拒绝策略;
        }
}

伪代码中线程里面的操作很像,但是为了好理解分开写了。执行顺序:
1、如果有任务来了,没有到达核心线程数,会立即新建一个线程执行,并且线程生命周期不会结束(terminal状态),因为run()方法没有走完,这个任务执行完后(r = null),线程会继续从阻塞队列里拿任务,拿不到会一直阻塞(take),默认不会停。
2、超过核心数,但是阻塞队列没有满,会把任务放在阻塞队列里;
3、阻塞队列满了,但是没有达到最大核心数,会新建一个线程,立马执行这个任务,执行完后从队列取任务,但是这时候是poll,是有超时退出的取,这个超时时间就是keepAliveTime。
5、队列满了,线程数也达到最大值了,线程池不知道怎么处理,用handler的策略来拒绝任务。

线程池的原理

1、根据上面分析,核心线程默认不会停的,线程的生命周期在run()方法执行完结束,首先会在while中循环,提交的任务执行完后还会从队列取,即使队列为空,也会阻塞在那等待;–复用线程
2、核心任务(没达到核心线程数时提交的任务)是不会加到队列里的,非核心任务才会加到队列里;用队列控制吞吐,是常用的设计思想之一。–控制吞吐

再来看看我们常用的几种线程池:
CachedThreadPool :

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

核心线程数为0,最大线程数无界(Integer.MAX_VALUE),60s后非核心线程退出,使用的队列是同步队列SynchronousQueue,SynchronousQueue是个无容量队列,所以不能通过peek方法获取头部元素;也不能单独插入元素,put和take是成对出现的,put一个元素,一定要等另一端take掉。所以CachedThreadPool一旦有任务,会立即新建一个非核心线程执行,由于是线程数无上限,所以一旦任务多,就会不停地新建线程,60s后还没执行完,就会超时退出。另一个角度,任务在小于且接近60s的时间里结束,那么可能会有大量的线程在同时进行,占用CPU,这种情况总结就是**“提交速度大于任务执行速度”,所以CachedThreadPool适合提交频繁,且执行时间短的任务**。
FixedThreadPool:

 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

核心线程数和最大线程数是一样的,也就是只有n个核心线程,而LinkedBlockingQueue是无界的队列(容量为Integer.MAX_VALUE),也就是不会满,也就不会产生非核心线程数,第一个任务结束后,核心线程会一直从队列里取任务不退出。
SingleThreadExecutor:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

核心线程数和最大线程数都是1,是FixedThreadPool的特例(n = 1),不同的是,由于只有一个核心线程,保证了所有任务都是在一个线程里有序执行。

Android线程池和内存优化

线程池和内存有什么关系?
1、核心线程的锅。由于核心线程默认不退出的特点,核心线程指向的对象一直都是运行中的,如果核心线程开的很大,那么这些线程对象在线程池使用shutdown之前,都是占内存的。
再看下面一个例子:

Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(@NonNull final Runnable r) {
                new Thread(){
                    @Override
                    public void run() {
                        r.run();
                    }
                };
                return null;
            }
        });

factory里的线程是匿名内部类,一旦这段代码放在Activity里,那么Thread对象会持有Activity的引用,而且是个强引用,那么外部的activity就一定会内存泄漏。
解决办法:

  1. ThreadFactory 通过声明为静态内部类;
  2. 如果在Activity中的线程池,有必要的话请考虑在onDestory的时候线程池shutdown。

2、阻塞队列的锅。像SingleThreadPool和FixThreadPool这两种线程池,队列大小是无界的,一旦线程处理的速度慢,而任务提交的快,就会有大量的任务堆积在队列里,从而在执行完之前造成一段时间的内存泄漏。
解决办法:合理设置线程池大小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值