关于线程池的学习和理解(一)

   一、线程和线程池

     关于线程大家都不陌生,官方说法线程作为进程执行的最小单位。线程池字面上理解就是一堆线程放在一起。打个比方,线程相当于小工,线程池相当于工程队。以前做任务A,招个小工过来,做任务B,招一个小工过来。。。。,这样的话就会出现一个问题,我招小工需要耗费时间精力,如果任务比较小,一会就做完了,那我费半天劲招个小工的时间都比干活的时间长,那我不是亏大了。因此,为了解决我频繁招工的问题,就需要建立一个工程队,这样就有了线程池的概念。有了工程队,现在工作的方式就变了,任务A来了,我招了小工甲,任务B来了,我招了小工乙,后面又来了任务C,小工甲刚好干完了,我直接就分配给小工甲,这次我就不用招工了。如果所有任务都做完了,我直接就解散工程队,整个操作行云流水,完美。总结一下,

   1、线程资源比较珍贵。如小工,我招聘要花时间吧,干活还要付费。

   2、线程池的出现解决了频繁招工的问题,减少系统创建线程的开销。

   3、线程池提高了工作效率,我可以把招工的时间用来干活。

  4、线程池可以更好的管理线程。有了工程队,有了组织,那些小工不是妥妥的听话。

综述所述,线程池非常有用。那线程池怎么用呢,这里就要介绍相关的东东了。

    二、如何创建线和使用线程池

    废话不多说,直接贴代码。

   ExecutorService executorService = Executors.newCachedThreadPool();

  executorService.submit(new Runnable(){});

上面提供了线程池创建的常用方式,Executors类作为线程池的工具类为我们提供了许多线程池创建的常用方法,主要有:

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

可以看到一般通过executor.submit来提交一个线程给线程池进行处理。Executors为一个工具类,在生成新的线程池时实际调用的是

ThreadPoolExecutor类的构造函数,如Executor.newCachedThreadPool()方法时,实际调用的如下:
public static ExecutorService newCachedThreadPool() {  
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  
                                  60L, TimeUnit.SECONDS,  
                                   new SynchronousQueue<Runnable>());  
 }。

那问题来了,ThreadPoolExecutor又是一个什么样的存在,作为线程池管理的核心类,如何如传说中的那样管理线程池,合理的调用线程资源来承接各种处理任务呢,下面我们一一道来。

    1、关键的ThreadPoolExecutor类

    ThreadPoolExecutor作为线程池创建的核心类具有四种构造方法,现在我拷贝一下网上介绍的一张图做下说明:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    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,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

可以看的出来ThreadPoolExecutor继承至AbstractExecutorService类,而AbstractExecutorService是接口ExecutorService 的实现,而ExecutorService接口继承Executor接口,那整个类的关系图如下:

类图上相关的接口和类我们稍后再介绍,我们继续说这个ThreadPoolExecutor的构造函数,从代码上看构造函数主要包含如下参数:

corePoolSize核心池的大小,这里要注意,线程池创建的时候里面是没有线程的,只有当有任务来的时候才创建线程。只有调用了prestartAllCoreThreads()或者prestartCoreThread()方法才会在线程池创建的时候创建线程,因此线程池创建的时候线程数为0。这里经常会有面试官挖坑问这个问题,请注意。
maximumPoolSize线程池最大线程数,表示线程池最多能创建多少线程。比如核心线程5个,最大线程10个,一般情况下就会有5个线程在排队。如果超过这个数字线程则阻塞。
keepAliveTime一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。
unit

指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。

TimeUnit是一个枚举类型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天

workQueue

线程池中的任务队列.

常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。一般会用LinkedBlockingDeque。如果线程池核心池大小为1,最大池为10,execute执行线程为10则会有9个线程在workQueue。

  • SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大

  • LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

  • ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

  • DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

threadFactory

线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法。

public interface ThreadFactory { Thread newThread(Runnable r); }

RejectedExecutionHandler

ejectedExecutionHandler也是一个接口,只有一个方法

public interface RejectedExecutionHandler {
  void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}

当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。

一般在workqueue容纳不下多余的线程时,会触发这个handler。

  

 

三、线程池规则

现在我们了解线程池主要工具类Executors及工具类创建的四种线程池及java实际执行线程池类ThreadPoolExecutor,现在我们来看看实际的规则。对照ThreadPoolExecutor构造函数的参数来看。

1、假设任务队列大小没有限制

(1)如果线程数量<corePoolSize,则直接启动核心线程来执行,不会加入workqueue任务队列。

 (2)如果线程数量>corePoolSize且<=maxPoolSize, workqueue为LinkedBlockingDeque时,超过corePoolSize的线程会进入任务队列中等待执行。

(3)如果线程数量>corePoolSize且<=maxPoolSize, workqueue为SynchronousQueue时,线程池会创建新线程进行执行,不会放到任务队列中,这些线程不属于核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。

(4)如果线程数量>maxPoolSize时,workqueue为LinkedBlockingDeque时,则会进入任务队列等待。由于任务队列没有大小限制,因此线程池的最大线程参数设置是无效的。当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。

2、假设任务队列大小固定

   (1)当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。

   (2)SynchronousQueue没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。

 

综上所属,线程池时什么,线程池的好处,怎么用线程池以及默认的ThreadPoolExecutor类默认的构造函数及参数规则都做了简单的描述。后续文章将梳理一下ThreadPoolExecutor如何启用线程,如果做线程管理等方面的源码分析。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值