关于java线程池,大家手写的可能不多,使用的话一般都是直接使用封装好的,具体内部的实现可能只是简单的了解下,一来是方便使用java线程池,当然还有一个更重要的作用,嘿嘿,大家都懂的。
其实理解java中一些源码的运行机制对大家的学习帮助是非常大的,一方面源码中的代码都十分精简,逻辑之间的管理很优雅,另一方面,熟悉源码之后,遇到现实生产中的问题,就很容易去关联到可能出现问题的某个节点,不然真拉出一大堆日志,然后一点点排查重试,特别是并发问题,真的能把人逼的崩溃哈。
今天就来说下线程池,我们由浅入深,一点点来哈,先贴一个构造方法,介绍几个关键的属性。线程池的关键ThreadPoolExecutor,大家尽量记住这个单词哈,不然每天都说线程池,结果不知道是哪个类,真被问到了,太尬了哈。看下构造方法
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //存活时间
TimeUnit unit, //存活时间单位
BlockingQueue<Runnable> workQueue, //缓存队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler //拒绝策略
) {
if (corePoolSize < 0 ||maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
线程池的构造方法几乎最终都是指向这个构造方法的。这个构造方法也就是对几个属性进行了限制,然后赋值,很好理解,我们分别来看下各个属性。
- corePoolSize:核心线程数,说到线程池,大家肯定要对线程池有个概念,这是一个存放多个线程的地方,好似一个池子一样,你需要的时候,就取出一个线程进行使用。因此,这个池子中肯定是要有线程的。当然,线程开多了,肯定会消耗服务器的性能,占用大量的资源。所以线程池在设计的时候就制定了设置线程池的策略。corePoolSize就是这种策略的一个参数。它的含义就是线程池一直会开着的线程,当然,也可以为0。不过最好不要,既然开了线程池,说明业务量并不小,线程的频繁创建与销毁也是要消耗资源的。
- maximumPoolSize:最大线程数,既然是一个池子,存放的数量肯定是有限制的,不然你传过来1千万数据,难道直接给你开1千万线程?别逗了,那样服务器早挂了~这里的最大线程数,就是一个限制,哪怕你传来的数据再多,一旦线程达到了最大线程数,就不允许在继续开线程了。当然这个参数不能设为0,不然你这个线程池还有啥意义?当然maximumPoolSize>corePoolSize是必须的。
- keepAliveTime 线程存活时间,举个例子,corePoolSize=2,maximumPoolSize=3,这个时候刚好没有任务需要线程池执行了,那么多余的一个线程就可以根据keepAliveTime 进行关闭了
- unit 时间单位,比如keepAliveTime是3,unit是TimeUnit.HOURS,这就表示3小时后关闭,unit是TimeUnit.SECONDS,那就表示3秒了。
- workQueue 缓存队列,刚才说了,线程最好不要一直开,那么需要执行的认为有好几千条怎么办?这个时候就要把需要执行的参数扔进缓存队列了,然后当有线程空闲下来的时候,就到缓存队列里面去取数据,这样就友好许多了。
- threadFactory 线程工厂
- handler 拒绝策略。 这两个后面再说。
好了,上面就是线程池最基本的参数了,不过要维持一个线程池高效运转,内部自然有一套自己的机制的。我们先从最近本的看起,看下这三个参数:corePoolSize,maximumPoolSize,workQueue。这三个参数是有明显的关系的,比如,线程多了,workQueue明显就可以少放点数据,还有什么时候放数据到workQueue,什么时候开线程?等等。
直接上代码,大家看的更清楚一些。
public class ThreadPoolTest {
public static BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
public static void main(String[] args) throws Exception {
// 创建一个核心线程为2,最大线程为8,缓存队列为10的线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 8, 0, TimeUnit.SECONDS, workQueue);
// 拒绝策略先不说,i最大值写小点
for (int i = 0; i < 18; i++) {
poolExecutor.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName());
//睡眠下,如果线程运行太快,直接空出来了,就没法试验了
Thread.sleep(200);
}
});
}
// 关闭下线程池,不能让它一直跑着哈
poolExecutor.shutdown();
}
我们建立了最大线程为8,缓存队列为10的线程池,同时塞进去18个任务,结果可以看到,启动了总共启动了8个线程,可以说是每个线程都用到了。
线程名:pool-1-thread-1
线程名:pool-1-thread-2
线程名:pool-1-thread-3
线程名:pool-1-thread-5
线程名:pool-1-thread-4
线程名:pool-1-thread-6
线程名:pool-1-thread-7
线程名:pool-1-thread-8
线程名:pool-1-thread-5
线程名:pool-1-thread-8
线程名:pool-1-thread-1
线程名:pool-1-thread-7
线程名:pool-1-thread-4
线程名:pool-1-thread-3
线程名:pool-1-thread-6
线程名:pool-1-thread-2
线程名:pool-1-thread-7
线程名:pool-1-thread-1
但是如果我们把任务改成17个呢?
for (int i = 0; i < 17; i++) {
poolExecutor.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName());
//睡眠下,如果线程运行太快,直接空出来了,就没法试验了
Thread.sleep(200);
}
});
}
就会发现,只启动了7个线程哦
线程名:pool-1-thread-2
线程名:pool-1-thread-3
线程名:pool-1-thread-6
线程名:pool-1-thread-7
线程名:pool-1-thread-1
线程名:pool-1-thread-5
线程名:pool-1-thread-4
线程名:pool-1-thread-3
线程名:pool-1-thread-2
线程名:pool-1-thread-4
线程名:pool-1-thread-5
线程名:pool-1-thread-1
线程名:pool-1-thread-7
线程名:pool-1-thread-6
线程名:pool-1-thread-2
线程名:pool-1-thread-3
线程名:pool-1-thread-7
核心线程肯定是启动了的,然后来了17个任务,线程开了7个,缓存队列10个,伙伴们是不是发现了什么?线程本来可以创建8个的,然后缓存队列放9个数据,可是现在是只创建了7个线程,缓存队列放了10个数据。很明显,线程池的运行或者说使用顺序是先coreSize > workQueue > maximumPoolSize,只有核心线程已经被占用,workQueue放满了之后,才会再次开启线程进行处理数据,直到开启到最大线程数。
那如果任务多了怎么办?比如来了100个任务。那当然是抛出异常啦,默认的拒绝策略是这么做的,当然你也可以自己定义拒绝策略,这个后面再说哈。
今天就先说到这了,有点简单,大家瞄一眼就好,莫要见笑哈。
No sacrifice,no victory~