Java中的线程池简述

一、常用的四种线程池

1、Executors.newCachedThreadPool()

创建一个可缓存的线程池,如果线程长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程(线程池为无限大,当执行第二个任务时,若第一个任务已经完成,则会复用执行第一个任务的线程,而不用每次新建线程),默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,内部使用SynchronousQueue作为阻塞队列;

	public void executeTaskInCachedThreadPool(Runnable runnable) {
        if (cachedThreadPool == null) {
            cachedThreadPool = Executors.newCachedThreadPool();
        }
        cachedThreadPool.execute(runnable);
    }

特点:在没有任务执行时,当前线程的空闲时间超过keepAliveTime,会自动释放线程资源;当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;因此,使用时要注意控制并发的任务数,防止因创建大量的线程导致性能降低。

2、Executors.newFixedThreadPool(int nThreads)

创建一个指定线程数的线程池,可控制最大并发数,超出的线程会在队列中等待。其中corePoolSize=maxPoolSize,使用LinkedBlockingQueue作为阻塞队列

	public void executeTaskInFixedThreadPool(Runnable runnable, int nThreads) {
        if (fixedThreadPool == null) {
            fixedThreadPool = Executors.newFixedThreadPool(nThreads);
        }
        fixedThreadPool.execute(runnable);
    }

特点:即使当前线程没有可执行任务时,也不会释放线程。

3、Executors.newSingleThreadExecutor()

创建一个只有一个线程的线程池,它只会用于唯一的工作线程来执行任务,保证所有的任务按照指定顺序(FIFO)执行,内部使用LinkedBlockingQueue作为阻塞队列。

public void executeTaskInSingleThreadExecutor(Runnable runnable) {
        ExecutorService singleThreadExecutor = 	Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(runnable);
    }

特点:如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交的任务顺利执行。

4、Executors.newScheduledThreadPool()

创建一个定长线程池,支持定时及周期性任务执行

	public void executeTaskInScheduledThreadPool(Runnable runnable, int corePoolSize, long delay, TimeUnit unit) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(corePoolSize);
        scheduledExecutorService.schedule(runnable, delay, unit);
    }

注意,这里schedule方法是延迟执行,其中runnable为任务,delay为延迟的时间,unit为延迟的时间单位。
除了shedule方法外,还有两个方法:sheduleAtFixtedRate()和sheduleWithFixedDelay,这两个方法用于执行周期性任务。

(1)sheduleAtFixedRate

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

比如,scheduleAtFixedRate(command, 5, 2, second)

第一次开始执行是5s后,假如执行耗时1s,那么下次开始执行是7s后,再下次开始执行是9s后。
如果执行耗时<等待时间,那么将会在任务执行完成后等待【等待时间-执行耗时】,然后再执行下一轮任务。
如果执行耗时>等于等待时间,那么将会在任务执行完成后立即开始执行下一轮任务。

(2)sheduleWithFixedDelay

  public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

比如,scheduleWithFixedDelay(command, 5, 2, second)

第一次开始执行是5s后,假如执行耗时1s,那么执行完成后的时间是6s。下次开始执行是8s后,再下次执行是11s后。它会在任务执行完成后再等待2秒。

特点:初始化的线程是可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期地同步数据。

总结:除了newScheduleThreadPool()的内部实现特殊一点之外,其他线程池内部都是基于ThreadPoolExecutor类(Executor的子类)实现的。

二、关于ThreadPoolExecutor构造函数

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

方法参数:

  • corePoolSize:核心线程数

  • maxmumPoolSize:最大线程数

  • keepAliveTime:线程存活时间(在corePoolSize<maxPoolSize情况下有用)

  • timeUnit:存活时间的时间单位

  • workQueue:阻塞队列(用来保存等待被执行的任务),关于workQueue参数的取值,JDK提供了四种阻塞队列类型:
    (1)ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
    (2)LinkedBlockingQueue:基于链表数据结构的阻塞队列,按FIFO排序任务。(容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQueue)
    (3)SynchronousQueue:一个不存储元素的阻塞队列,每个插入的操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。
    (4)PriorityBlockingQueue:具有优先级的无界阻塞队列,默认情况下元素采用自然顺序升序排列。
    (5)DelayQueue:延迟队列,任务定时周期延迟执行的队列,根据指定的时间从小到大排序,否则根据插入到队列的先后排序。(newScheduledThreadPool线程池使用了这个队列)

  • threadFactory: 线程工厂,主要用来创建线程

  • handler:标识当拒绝处理任务时的策略时(当阻塞队列满了,且没有空闲的工作线程时,如果继续提交任务,必须采取一种策略处理该任务),有以下四种取值:
    (1)ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。(使用这个策略必须处理抛出的异常,否则会打断当前的执行流程,影响后续的任务执行)
    (2)ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    (3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃阻塞队列workQueue中最老的任务,然后重新尝试执行任务
    (4)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。(交给线程池调用所在的线程进行处理。由于是主线程自己处理,相当于没有用线程池,一般并发比较小,性能要求不高可以用,否则可能导致程序阻塞)

三、线程池提交任务的方式(2种)

1、Executor.execute(Runnable command)

2、ExecutorService.submit(Callable task)

区别:
(1)二者接收的参数不一样
execute()方法只能接收Runnable类型的参数,而submit()方法可以接收Callable、Runnable两种类型的参数。Callable类型的任务是可以返回执行结果的,而Runnable类型的任务不可以返回执行结果。Callable是JDK1.5加入的执行目标接口,作为Runnable的一种补充,允许有返回值,允许抛出异常。Runnable和Callable的主要区别为:Callable允许有返回值,Runnable不允许有返回值;Runnable不允许抛出异常,Callable允许抛出异常。
(2)submit()提交任务后会有返回值,而execute()没有
execute()方法主要用于启动任务的执行,而任务的执行结果和可能的异常调用者并不关心。submit()方法也用于启动任务的执行,但是启动之后会返回Future对象,代表一个异步执行实例,可以通过该异步执行实例去获取结果。
(3)submit()方便Exception处理
execute()方法在启动任务执行后,任务执行过程中可能发生的异常调用者并不关心。而通过submit()方法返回的Future对象(异步执行实例),可以进行异步执行过程中的异常捕获。

四、线程池的关闭(2种)

1、shutdown()
不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
2、shutdownNow():
立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。

五、线程池的几种状态

1、RUNNING
运行状态,能够接受新的任务且会处理阻塞队列中的任务
2、SHUTDOWN
关闭状态,不接受新任务,但是会处理阻塞队列中的任务,执行线程池的shutDown()对应的就是此状态
3、STOP
停止状态,不接受新的任务,也不会处理等待队列中的任务并且会中断正在执行的任务。调用线程池的shutDownNow()对应的是此状态
4、TIDYING
整理,即所有的任务都停止了,线程池中线程数量等于0,会调用terminated(),如果自己实现线程池的话。
5、TERMINATED
结束状态,terminated()方法执行完了。

六、问题

1、JDK线程池具体是如何区分核心线程和非核心线程的?
答:分情况讨论,如果allowCoreThreadTimeOut为true,那么对于线程池而言,核心线程和非核心线程最终处理是一样的;如果allowCoreThreadTimeOut为false,则是根据工作线程数量是否大于corePoolSize属性,大于则为非核心线程,否则为非核心线程
2、核心线程真的可以一直存活吗?
答:如果allowCoreThreadTimeOut为true,则工作队列为空,核心线程一直获取不到任务对象时,核心线程也会像非核心线程一样消亡。(ThreadPoolExecutor的allCoreThreadTimeOut方法)
3、非核心线程为什么只能存在一段时间?
答:如果Worker对象被判断为非核心线程,从workQueue工作队列中获取任务线程时会有时间限制,如果超出时间还没有获取到任务线程,Worker对象将会消亡;但是allowCoreThreadTimeOut为false核心线程使用的是阻塞获取,将永远不会返回null,因此核心线程这种情况可以一直存在。

七、总结

当提交一个任务时,线程池会创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务会被保存到阻塞队列中,等待被执行;如果阻塞队列满了,那就创建新的线程执行当前任务,直到线程池中的线程数达到maxPoolSize,这时再有任务来,只能执行reject()处理该任务;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值