Java线程池

线程池的定义

Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。线程池就是将线程进行池化,需要运行任务时从池中拿一个线程来执行,执行完毕,线程放回池中。
在开发过程中,合理地使用线程池能够带来3个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

线程池各个参数的含义

ThreadPoolExecutor有四个构造函数,但最终调用的都是同一个:

 public ThreadPoolExecutor(
	int corePoolSize,//线程池核心线程数量
	int maximumPoolSize,//线程池最大数量
	long keepAliveTimet,//空闲线程存活时间
	TimeUnit unit,//时间单位
	BlockingQueue workQueue,//线程池使用的缓冲队列
	ThreadFactory threadFactory,//线程池创建线程使用的工厂类
	RejectedExecutionHandler handler//线程池对拒绝任务的处理策略
	)

线程池的工作机制

Java线程池_第1张图片

这里有一点需要注意是:当核心线程已满,队列未满的时候,这时新来的请求会加入队列等待;而此时假如队列恰好满了,这时新来的任务就会创建非核心线程执行任务。这样就会出现一个问题,前面来的任务还在队列中等待,而后来的任务却在非核心线程中得到执行!

Executors创建ThreadPoolExecutor的问题

Executors创建ThreadPoolExecutor对象的方法有三种:

  • Executors#newCachedThreadPool => 创建可缓存的线程池
  • Executors#newSingleThreadExecutor => 创建单线程的线程池
  • Executors#newFixedThreadPool => 创建固定长度的线程池

Executors#newCachedThreadPool方法

public static ExectorService new CachedThreadPool(){
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}

CachedThreadPool是一个根据需要创建新线程的线程池,当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队列永远是满的,因此最终会创建非核心线程来执行任务。非核心线程空闲60s后会被回收。
因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常

Executors#newSingleThreadExecutor方法

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

SingleThreadExecutor是单线程线程池,只有一个核心线程。当提交一个任务时,首先创建一个核心线程,再有就会放入队列,LinkedBlockingQueue是长度为Integer.MAX_VALUE的队列,基本可视为无界队列,所以无论maximumPoolSize和keepAliveTime参数是多少,都可视为无效,因为所有的任务都会放入队列中。在资源有限的时候容易引起OOM异常

Executors#newFixedThreadPool方法

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

它和SingleThreadExecutor唯一区别就是核心线程的数量!由于使用的是LinkedBlockingQueue,在资源有限的时候容易引起OOM异常

如何合理配置线程池

合理配置线程池大小
性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。

避免使用无界队列
不要使用Executors.newXXXThreadPool()快捷方法创建线程池,因为它们不是可创建线程太多就是队列可缓存太多线程,都可能造成OOM。所以最好是我们自己使用ThreadPoolExecutor的构造方法手动指定队列的最大长度。
常用阻塞队列有:

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
ExecutorService executorService = new ThreadPoolExecutor(2, 2, 
                0, TimeUnit.SECONDS, 
                new ArrayBlockingQueue<>(512), // 使用有界队列,避免OOM
                new ThreadPoolExecutor.DiscardPolicy());

你可能感兴趣的:(java,Java,线程池)