一万小时学编程系列
《一万小时定律》格拉德威尔:“人们眼中的天才之所以卓越非凡,并非天资超人一等,而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成世界级大师的必要条件。”
前言
线程池是学习Java的重点也是难点,在面试中也会经常问到,尤其是对“高并发”有较高要求的企业,所以学好线程池原理很重要,本文是我参考大佬的博客写的,后边有链接地址。闲话不多说,让我们开始学习吧。
一、并发队列
1.概念
并发队列是一个基于链接点的无界线程安全队列,它采用先进先出的规则对节点进行排序,添加元素是在队列尾部,获取返回队列头部元素。
2.分类
并发队列分为阻塞队列和非阻塞队列,下面举例说明两者的区别。
非阻塞队列:入队操作,如果容器满了则会丢失。出队操作如果容器空返回null。
阻塞队列:入队操作,如果容器满了则会等待,有出队操作之后再入队。出队操作时如果容器空了则会等待,有入队操作之后再出队。
线程池就是基于阻塞队列实现。
二、线程池简介
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
一个线程池包括以下四个基本组成部分:
- 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
- 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
- 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
- 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
三、线程池原理
1.ThreadPoolExecutor核心类
线程池最上层接口是Executor,这个接口定义了一个核心方法execute(Runnablecommand),该方法是用来传入任务的,最后被ThreadPoolExecutor类实现。ThreadPoolExecutor是线程池核心类,构造方法如下:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnitunit,BlockingQueue<Runnable>workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
各个参数表示意义:
参数名 | 参数含义 |
---|---|
corePoolSize | 核心线程池大小,也即核心线程的数量 |
maximumPoolSize | 最大线程池大小,也即线程的最大数量 |
keepAliveTime | 空闲时间,是除核心线程之外的新创建线程的最大存活时间 |
TimeUnit | 时间单位 |
workQueue | 阻塞队列,用来存储等待的任务 |
threadFactory | 线程工厂,用来创建新线程 |
handler | 拒绝处理策略,当提交给线程池的任务量超过最大线程池大小+队列长度,就会采取拒绝处理策略 |
2.线程池原理图
过程描述如下:用户提交任务,线程池开始工作,当任务数小于核心线程数时,任务直接分配给核心线程执行。当任务数比核心线程数大时,这时所有核心线程都已分配任务,未分配完的储存在任务队列中,当核心线程完成之后执行。如果队列满了还有源源不断的任务到来,此时线程池会创建新线程开始执行任务,此时核心线程和新线程开始执行任务,如果,还是有任务提交,核心线程和新线程都在执行,队列都满了,那么此时线程池会报错拒绝任务,等到队列有空位时再接受任务。同样当任务数量减少,队列优先将任务交给核心队列处理,当新线程没有任务处理时就会自动关闭。值得注意的是这是一个动态的过程,其实还是蛮好理解的,
3.线程池实例
通过一个例子来了解线程池的基本原理。
public class Test{
public static void main(String[] args){
ThreadPoolExecutor pool=new ThreadPoolExecutor(1,2,3,TimeUnit.SECONDS,new LinkedBlockingDeque<>(3));
for(int i=0;i<7;i++){
pool.execute(new TestThread());
}
pool.shutdown();
}
}
class TestThread implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
构造函数:ThreadPoolExecutor(1,2,3,TimeUnit.SECONDS,new LinkedBlockingDeque<>(3))中五个参数的含义。
- 1:核心线程数为1
- 2:最大线程数为2,所以可以新建一个线程
- 3:空闲时间,新建线程执行任务后等待新任务的空闲时间
- TimeUnit.SECONDS:时间单位,秒
- new LinkedBlockingDeque<>(3):阻塞队列,长度为3
运行结果:执行到第6条时开始报错。线程池拒绝新任务
总结
>以上就是今天要讲的内容,本文仅仅简单介绍了线程池的原理,而线程池更深刻的含义等待大家去挖掘,多读源码,多看帖子是最有效的提升之道。本文是参考大佬的博客写的,有不足的地方欢迎指正,如果本文对你有所帮助的话请关注点赞,你的鼓励是我最大的动力。
@我在风花雪月里等你:JAVA-线程池的基本原理https://blog.csdn.net/wei_zhi/article/details/52767651