谷粒商城高级篇(37)——线程池的使用

线程池基本使用

初始化线程的4种方式

  1. 继承Thread
  2. 实现Runnable接口
  3. 实现Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)
  4. 线程池

方式1和方式2:主进程无法获取线程的运算结果。不适合当前场景

方式3:主进程可以获取线程的运算结果,并设置给itemVO,但是不利于控制服务器中的线程资源。可以导致服务器资源耗尽。

方式4:通过如下两种方式初始化线程池:

Executors.newFiexedThreadPool(3);
//或者
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit unit, workQueue, threadFactory, handler);

不建议使用第一种方式初始化线程池

线程池构造参数介绍

corePoolSize
核心线程数,表示在线程池初始化过程中一直存在的线程数

maximumPoolSize
最大线程数,表示一个线程池创建过程中最大拥有的线程的数量

keepAliveTime
线程空闲时存活时间,表示一个线程在空闲的时候如果超过空闲时存活时间,那么这个线程有可能被销毁(核心线程除外)

unit
顾名思义,表示空闲时存活时间的单位

workQueue
任务队列。被提交但尚未执行的任务存放在这里。

threadFactory
线程工厂。用于生成线程池中工作线程的工厂,用于创建线程,一般使用默认的即可

handler
拒绝策略。当队列满了,并且工作线程已经达到maximumPoolSize时,如何拒绝请求执行的Runnable的策略。

拒绝策略

AbortPolicy
当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。

CallerRunsPolicy

当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务。
DiscardOldestPolicy

当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。
DiscardPolicy

当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。

为什么阿里开发手册不允许使用常见线程池

我们先分析一下以上几种创建线程池的源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}


public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}
//newSingleThreadScheduledExecutor稍有不同,其创建线程池的核心方法如下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
    new ThreadPoolExecutor(1, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

不难看出核心为new ThreadPoolExecutor(…)方法。

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

FixedThreadPool

对于FixedThreadPool定长线程池,我们来看一看它的源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

可以看到,定长线程池的核心线程数和最大线程数是相同的,在并发量较高的情况下,如果线程数设置的比较小的话,那么会有大量的任务进入队列中。FixedThreadPool使用的等待队列源码如下:

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}

也就是说,FixedThreadPool使用了一个容量为Integer.MAX_VALUE的队列,可是int的最大值为2^31-1,一般情况下根本无法容纳这么对任务对象,所以当任务量过大的时候,使用FixedThreadPool很容易发生OOM,内存溢出。

SingleThreadExecutor

我们再来分析一下SingleThreadExecutor单一线程池的源码

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

单一线程池只创建了一个线程,且使用的队列与定长线程池相同,易发生同样的问题。

CacheThreadPool

再来分析一下CacheThreadPool可缓存线程池的源码:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

与前两种线程池不同的是,可缓存线程池的核心线程数为0,最大线程数为int类型的最大值。显然我们无法创建这么大数量的线程,在并发任务量较大的情况下容易发生内存溢出。而且核心线程池为0,可能会导致频繁创建和销毁线程,增加了系统的开销。

ScheduledThreadPool

可延迟线程池ScheduledThreadPool:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

可以看到,可延迟线程池的最大线程数为int类型的最大值,与可缓存线程池同样会发生内存溢出的问题。

综上所述,JDK为我们创建好的Executors类在真正的生产环境开发中,我们是不去使用的,容易引发内存溢出,导致程序崩溃。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值