Java线程池原理剖析

  • Java线程池原理剖析已关闭评论
  • 3,801次阅读
  • A+
所属分类:Java

Java线程池原理剖析

今天又巩固巩固了线程池的用法,记录下它的原理以及使用方式。

为什么要使用线程池?

首先抛出一个问题,我们为什么要使用线程池?众所周知,在应用开发的时候或多或少的都要使用多线程技术去异步或并发的执行一些任务,但是如果我们频繁的去创建线程、销毁线程无疑会带来很多的性能开销,所以我们选择使用线程池,能合理的运用线程,降低资源浪费。使用线程池会有3个好处,如下

1.降低资源消耗 不需要创建过多的线程,重复利用已创建好的线程,降低多次创建或销毁线程的开销。

2.提高程序响应速度 当使用了多线程跑任务或者异步执行程序的话,会大大提高应用的响应速度,尽可能的“压榨”计算机资源提高程序性能

3.合理的管理线程 如果未使用线程池的话,随处创建线程或销毁线程,在编码方面涉及多线程的代码很变的杂乱无章,难以管理。使用线程池可以使用线程工厂,合理的给各个线程起一些具有业务含义的名字,这样在排查问题能快速定位问题以及解决问题。

线程池的原理

线程池在初始化的时候需要设置很多参数,其实也是多线程面试高频问题,这里一一说明下。
1.corePoolSize
线程池的核心线程数 当往线程池中提交线程时,首先会判断线程池中的核心线程数是否已满,如果没满,则创建一个新的线程,如果已经满了则将线程放入工作队列中
2.maximumPoolSize
线程池最大线程数 意味着这个线程池最多能放多少个线程,如果工作队列满了且当前池子里的线程数小于最大线程数,则线程池还会继续创建新的线程来执行任务,直到线程池达到最大线程数。(值得注意的是,如果使用无界队列的话,这个参数就没什么意义了,因为使用无界队列的话,工作线程队列是永远不会满的。)如果线程池以达到最大线程数,依然提交线程的话,这时候就会触发饱和策略。
3.runnableTaskQueue
任务队列 当核心线程数满了后,再往线程池里提交线程的话,线程池就会把提交后的线程放入任务队列中。
工作中一般会使用到的队列如下:
- ArrayBlockingQueue : 基于数组结构的有界阻塞队列。
- LinkedBlockingQueue:基于链表结构的阻塞队列,吞吐量高于ArrayBlockingQueue。
- SynchronousQueue:一个不存储元素的阻塞队列,每一个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量高于LinkedBlockingQueue。
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

4.ThreadFacotry
用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
5.keepAliveTime
线程池如果存在有空闲的线程,则通过这个属性来保持存活时间。如果任务多,每个任务执行的时间较短,就可以把这个参数时间调大点,最大限度的利用线程。
6.TimeUnit
线程池存活时间的单位,可以设置为毫秒、微妙、秒、分钟、小时、天等。
7.RejectedExecutionHandler
饱和策略,上面最大线程数参数已经说了,如果线程池里的队列满了,并且已经超出了最大线程数,则会触发饱和策略。线程池提供四种默认的饱和策略
- AbortPolicy: 直接抛出异常,也是线程池默认的饱和策略。
- CallerRunsPolicy: 只用调用者所在线程来运行任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,直接丢弃。
也可以通过实践RejectedExecutionHandler接口来自定义饱和策略,比如我们在跑任务的时候,如果线程池触发饱和策略的话,可以进行记录日志或者持久化不能处理的任务。

线程池的执行流程

上面列出了线程池使用到的一些核心参数,我们就根据这些参数刻画出线程池真正运行的流程图。

Java线程池原理剖析

流程图中已经很清楚了,具体流程再用文字描述一下:调用者在创建线程池后,往线程池中提交线程时,如果当前线程数小于核心线程数,那么线程池会创建新的线程执行任务,如果当前线程数大于核心线程数,则将当前线程放入工作队列中,待工作队列中的线程数满了后,则会判断当前线程数是否超过最大线程数,如果没有超过最大线程数,则继续创建新的线程跑任务,如果当前线程池中的线程数已经超出了最大线程数,则表示当前线程池已经饱和了,无法再去提交新的线程数,则执行线程池的饱和策略,策略可以自定义,也可以使用线程池自带的4中饱和策略,按照自己的业务需求定义。

代码实战

接下来的环节,我们来看看具体线程池代码,是如何运行的。其实代码很简单,主要是线程池执行的具体流程。

初始化

//线程池的初始化内容,其实线程池的精髓也是在参数的配置,这里我们暂时先配置如下参数
 LinkedBlockingQueue queue = new LinkedBlockingQueue(5);
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 5, 20000, TimeUnit.MILLISECONDS, queue);

//线程任务比较简单
package basic.a;

public class RunTask implements Runnable {
    private String name;

    public RunTask(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
            System.out.println(name + " 执行任务...");
        } catch (Exception e) {
        }
    }
}

核心参数与任务队列

try {
            for (int i = 0; i < 5; i++) {
                String name = "Thread" + i;
                RunTask runTask = new RunTask(name);
                threadPoolExecutor.execute(runTask);
                System.out.println("当前队列里有多少个任务---> " + threadPoolExecutor.getQueue().size());
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            threadPoolExecutor.shutdown();
        }

我们首先看下核心参数与任务队列,这里我增加了5个线程数,以上面流程的介绍,核心线程1个,工作队列是有界队列(最大数值是5),在往线程池中增加5个线程,那么最终核心线程会创建1个,工作队列中将有4个线程数,运行代码结果如下:

当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 1
当前队列里有多少个任务---> 2
当前队列里有多少个任务---> 3
当前队列里有多少个任务---> 4
Thread0 执行任务...
Thread1 执行任务...
Thread2 执行任务...
Thread3 执行任务...
Thread4 执行任务...

看到结果后,最终队列里有4个线程,1个核心线程,当然核心线程执行完当前任务,会从队列里中取待执行的线程。

最大线程数与饱和策略

再来看下最大线程数的配置

LinkedBlockingQueue queue = new LinkedBlockingQueue(50);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20, 50, 20000, TimeUnit.MILLISECONDS, queue);
for (int i = 0; i < 100; i++) {
                String name = "Thread" + i;
                RunTask runTask = new RunTask(name);
                threadPoolExecutor.execute(runTask);
                System.out.println("当前队列里有多少个任务---> " + threadPoolExecutor.getQueue().size());
            }

这里我配置了核心线程数20,最大线程数50,队列里可以存放50个线程,按照线程池的运行机制,线程池中最多可以存放50个线程数,这里我循环创建了100个线程,当队列里的线程数满50个后,再去判断当前线程池里的线程数是否超过50,如果没有超过50,则继续创建线程,如果超过50个线程,则通过饱和策略直接报错。

当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 0
当前队列里有多少个任务---> 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
当前队列里有多少个任务---> 39
当前队列里有多少个任务---> 40
当前队列里有多少个任务---> 41
当前队列里有多少个任务---> 42
当前队列里有多少个任务---> 43
当前队列里有多少个任务---> 44
当前队列里有多少个任务---> 45
当前队列里有多少个任务---> 46
当前队列里有多少个任务---> 47
当前队列里有多少个任务---> 48
当前队列里有多少个任务---> 49
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
当前队列里有多少个任务---> 50
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task basic.a.RunTask@490d6c15 rejected from java.util.concurrent.ThreadPoolExecutor@27082746[Running, pool size = 25, active threads = 25, queued tasks = 50, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
    at basic.a.A.main(A.java:14)
Thread0 执行任务...
Thread3 执行任务...
Thread4 执行任务...
Thread6 执行任务...
Thread2 执行任务...
Thread1 执行任务...
Thread5 执行任务...
Thread7 执行任务...
Thread9 执行任务...
Thread8 执行任务...
Thread60 执行任务...
Thread63 执行任务...
Thread65 执行任务...
Thread64 执行任务...
Thread61 执行任务...
Thread62 执行任务...
Thread66 执行任务...
Thread69 执行任务...
Thread67 执行任务...
Thread72 执行任务...
Thread73 执行任务...
Thread74 执行任务...
Thread71 执行任务...
Thread70 执行任务...
Thread68 执行任务...
Thread11 执行任务...
Thread14 执行任务...
Thread12 执行任务...
Thread13 执行任务...
Thread10 执行任务...
Thread16 执行任务...
Thread15 执行任务...
Thread18 执行任务...
Thread19 执行任务...
Thread17 执行任务...
Thread22 执行任务...
Thread26 执行任务...
Thread20 执行任务...
Thread24 执行任务...
Thread25 执行任务...
Thread23 执行任务...
Thread21 执行任务...
Thread30 执行任务...
Thread33 执行任务...
Thread27 执行任务...
Thread28 执行任务...
Thread34 执行任务...
Thread32 执行任务...
Thread29 执行任务...
Thread31 执行任务...
Thread36 执行任务...
Thread41 执行任务...
Thread39 执行任务...
Thread38 执行任务...
Thread35 执行任务...
Thread37 执行任务...
Thread40 执行任务...
Thread42 执行任务...
Thread43 执行任务...
Thread44 执行任务...
Thread45 执行任务...
Thread49 执行任务...
Thread50 执行任务...
Thread52 执行任务...
Thread54 执行任务...
Thread55 执行任务...
Thread56 执行任务...
Thread57 执行任务...
Thread58 执行任务...
Thread59 执行任务...
Thread47 执行任务...
Thread46 执行任务...
Thread53 执行任务...
Thread51 执行任务...
Thread48 执行任务...

  • 我的微信
  • 加好友一起交流!
  • weinxin
  • 微信公众号
  • 关注公众号获取分享资源!
  • weinxin