专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java线程之Executors线程池的原理和使用

temp10 2024-09-16 05:29:48 java教程 13 ℃ 0 评论

问:生产环境如何使用线程池?

答:参见文章最底部。

Java线程之Executors线程池的原理和使用


Java中最著名和最常用的线程池当然是Executors。

Executors提供了好几种工厂方法来创建线程池,内部都是用了ThreadPoolExecutor,配合着不同的BlockingQueue和RejectExceptionHandler一起工作。

Executors.newSingleThreadExecutor

ExecutorService executorService1 = Executors.newSingleThreadExecutor();

我们来看一下这个方法的源码:

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

newSingleThreadExecutor会创建一个具有单一线程的线程池。也就是说创建的线程池中只会有一个线程。我们可以不断的往这个线程池里提交新的Runnable执行,但这些提交的Runnable只会串行的执行。

newSingleThreadExecutor配合着LinkedBlockingQueue一起工作。LinkedBlockingQueue是无界的阻塞队列,当不断有新任务(Runnable)提交时,可能抛出OutOfMemoryError。在日常使用的过程中需要注意这点。这也是我们不推荐在生产环境使用它的原因!

Executors.newFixedThreadPool

ExecutorService executorService2 = Executors.newFixedThreadPool(10);

newFixedThreadPool会创建一个具有固定数量线程的线程池。也就是说,创建的线程池中线程数量是固定值,在任何时候,这个线程池中都会那个固定数量的线程在运行。

我们来看一下源码:

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

可以看到,它与newSingleThreadExecutor不用的地方就是ThreadPoolExecutor方法中的前两个参数。newFixedThreadPool把最小存活线程数量和最大线程数量都设成了nThreads,也就是参数值。同样的,newFixedThreadPool也是配合着LinkedBlockingQueue一起工作。当不断有新任务(Runnable)提交时,可能抛出OutOfMemoryError。这同样也是我们不推荐在生产环境使用它的原因!

注意:

  1. newFixedThreadPool是固定大小的线程池;
  2. corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;
  3. keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉,但这里keepAliveTime无效;
  4. 阻塞队列采用了LinkedBlockingQueue,是一个无界队列;
  5. 由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。

Executors.newScheduledThreadPool

ExecutorService executorService3 = Executors.newScheduledThreadPool(10);

这是一个调度线程池,支持定时及周期性任务执行。

用法如下:

延迟3秒执行

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(int corePoolSize);

// 3秒后输出schedule run
scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
        log.warn("schedule run");
    }
}, 3, TimeUnit.SECONDS);

周期性执行


ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

// 每隔3秒输出schedule run
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        log.warn("schedule run");
    }
}, 0, 3, TimeUnit.SECONDS);


来看一下newScheduledThreadPool的源码:

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

看一下super方法调用的是什么:

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

newScheduledThreadPool源码中的ScheduledThreadPoolExecutor类实现了ScheduledExecutorService接口。maximumPoolSize被设置成了Integer.MAX_VALUE。因此,当不断有新任务(Runnable)提交时,如果阻塞队列(delayedWorkQueue)满了,会不断创建新线程来执行任务,可能抛出OutOfMemoryError。这同样也是我们不推荐在生产环境使用它的原因!

Executors.newCachedThreadPool

ExecutorService executorService4 = Executors.newCachedThreadPool();

这将会创建一个具备缓冲能力的线程池。

来看一下源码:

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

可以看到,ThreadPoolExecutor构造函数中的maximumPoolSize参数被设置成了Integer.MAX_VALUE,当不断有新任务提交(Runnable)时,如果阻塞队列满了,会不断创建新线程来执行任务(Runnable),可能抛出OutOfMemoryError。这同样也是我们不推荐在生产环境使用它的原因!

注意:

  1. newCachedThreadPool是一个可以无限扩大的线程池;
  2. newCachedThreadPool比较适合处理执行时间比较小的任务;
  3. newCachedThreadPool的corePoolSize为0,maximumPoolSize为无限大,意味着线程数量可以无限大;
  4. keepAliveTime为60S,意味着线程空闲时间超过60S就会被销毁;
  5. 采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。

生产环境使用线程池?

问:既然Executors线程池中的方法都不推荐在生产环境使用,那么我们生产环境该怎么创建线程池?

答:可以看考《阿里巴巴Java开发手册》。



私信“阿里巴巴”,即可获得《阿里巴巴Java开发手册》!

私信“阿里巴巴”,即可获得《阿里巴巴Java开发手册》!

私信“阿里巴巴”,即可获得《阿里巴巴Java开发手册》!

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表