专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java线程池详解(一)-创建自有线程池

temp10 2024-11-23 22:32:40 java教程 13 ℃ 0 评论

什么是线程池

线程池的概念大家应该都很清楚,帮我们重复管理线程,避免创建大量的线程增加开销。除了降低开销以外,线程池也可以提高响应速度

创建线程池需要使用 ThreadPoolExecutor 类,它的构造函数参数如下:

Java线程池详解(一)-创建自有线程池

public ThreadPoolExecutor(int corePoolSize, //核心线程的数量
 int maximumPoolSize, //最大线程数量
 long keepAliveTime, //超出核心线程数量以外的线程空余存活时间
 TimeUnit unit, //存活时间的单位
 BlockingQueue<Runnable> workQueue, //保存待执行任务的队列
 ThreadFactory threadFactory, //创建新线程使用的工厂
 RejectedExecutionHandler handler // 当任务无法执行时的处理器
 ) {...}
public void execute(Runnable command) {
 if (command == null)
 throw new NullPointerException();
 
 int c = ctl.get();
 //1.当前池中线程比核心数少,新建一个线程执行任务
 if (workerCountOf(c) < corePoolSize) {
 if (addWorker(command, true))
 return;
 c = ctl.get();
 }
 //2.核心池已满,但任务队列未满,添加到队列中
 if (isRunning(c) && workQueue.offer(command)) {
 int recheck = ctl.get();
 if (! isRunning(recheck) && remove(command)) //如果这时被关闭了,拒绝任务
 reject(command);
 else if (workerCountOf(recheck) == 0) //如果之前的线程已被销毁完,新建一个线程
 addWorker(null, false);
 }
 //3.核心池已满,队列已满,试着创建一个新线程
 else if (!addWorker(command, false))
 reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}

可以看到,线程池处理一个任务主要分三步处理,代码注释里已经介绍了,我再用通俗易懂的例子解释一下:

(线程比作员工,线程池比作一个团队,核心池比作团队中核心团队员工数,核心池外的比作外包员工)

  1. 有了新需求,先看核心员工数量超没超出最大核心员工数,还有名额的话就新招一个核心员工来做 需要获取全局锁
  2. 核心员工已经最多了,HR 不给批 HC 了,那这个需求只好攒着,放到待完成任务列表吧
  3. 如果列表已经堆满了,核心员工基本没机会搞完这么多任务了,那就找个外包吧 需要获取全局锁
  4. 如果核心员工 + 外包员工的数量已经是团队最多能承受人数了,没办法,这个需求接不了了

由于 1 和 3 新建线程时需要获取全局锁,这将严重影响性能。因此 ThreadPoolExecutor 这样的处理流程是为了在执行 execute() 方法时尽量少地执行 1 和 3,多执行 2。

在 ThreadPoolExecutor 完成预热后(当前线程数不少于核心线程数),几乎所有的 execute() 都是在执行步骤 2。

ThreadPoolExecutor 构造函数的参数

  1. corePoolSize:核心线程池数量

在线程数少于核心数量时,有新任务进来就新建一个线程,即使有的线程没事干

等超出核心数量后,就不会新建线程了,空闲的线程就得去任务队列里取任务执行了

  1. maximumPoolSize:最大线程数量

包括核心线程池数量 + 核心以外的数量

如果任务队列满了,并且池中线程数小于最大线程数,会再创建新的线程执行任务

  1. keepAliveTime:核心池以外的线程存活时间,即没有任务的外包的存活时间

如果给线程池设置 allowCoreThreadTimeOut(true),则核心线程在空闲时头上也会响起死亡的倒计时

如果任务是多而容易执行的,可以调大这个参数,那样线程就可以在存活的时间里有更大可能接受新任务

  1. workQueue:保存待执行任务的阻塞队列

不同的任务类型有不同的选择

  1. threadFactory:每个线程创建的地方

可以给线程起个好听的名字,设置个优先级啥的

  1. handler:饱和策略,大家都很忙,咋办呢,有四种策略

CallerRunsPolicy:只要线程池没关闭,就直接用调用者所在线程来运行任务

AbortPolicy:直接抛出 RejectedExecutionException 异常

DiscardPolicy:悄悄把任务放生,不做了

DiscardOldestPolicy:把队列里待最久的那个任务扔了,然后再调用 execute() 试试看能行不

我们也可以实现自己的 RejectedExecutionHandler 接口自定义策略,比如如记录日志什么的

保存待执行任务的阻塞队列

当线程池中的核心线程数已满时,任务就要保存到队列中了。线程池中使用的队列是 BlockingQueue 接口,常用的实现有如下几种:

  1. ArrayBlockingQueue:基于数组、有界,按 FIFO(先进先出)原则对元素进行排序
  2. LinkedBlockingQueue:基于链表,按FIFO (先进先出) 排序元素 吞吐量通常要高于 ArrayBlockingQueue Executors.newFixedThreadPool() 使用了这个队列
  3. SynchronousQueue:不存储元素的阻塞队列 每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态 吞吐量通常要高于 LinkedBlockingQueue Executors.newCachedThreadPool使用了这个队列
  4. PriorityBlockingQueue:具有优先级的、无限阻塞队列


◆ ◆ ◆ ◆ ◆

关注并后台回复 “面试”,即可免费获取

最新2019BAT大厂面试题,更有大数据

视频等你拿

·END·

后端开发技术

追求技术的深度

vx:后端开发技术

创建线程池

1、先定义线程池的几个关键属性的值:

设置核心池的数量为 CPU 数的两倍,一般是 4、8,好点的 16 个线程

最大线程数设置为 64

空闲线程的存活时间设置为 1 秒

如何设置核心线程数
  1. IO密集型 CPU处理器个数*2

IO密集型,是指系统大部分时间在跟I/O交互,而这个时间线程不会占用CPU来处理

  1. CPU密集型 CPU处理器个数+1

CPU密集型,是指系统大部分时间是在做程序正常的计算任务,例如数字运算、赋值、分配内存、内存拷贝、循环、查找、排序等,这些处理都需要CPU来完成。

2、根据处理的任务类型选择不同的阻塞队列

如果是要求高吞吐量的,可以使用 SynchronousQueue 队列;如果对执行顺序有要求,可以使用 PriorityBlockingQueue;如果最大积攒的待做任务有上限,可以使用 LinkedBlockingQueue。

3、创建自己的 ThreadFactory

在其中为每个线程设置个名称

4、创建线程池

public class ThreadPoolManager {
 private final String TAG = this.getClass().getSimpleName();
 private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2; // 核心线程数为 CPU数*2
 private static final int MAXIMUM_POOL_SIZE = 64; // 线程队列最大线程数
 private static final int KEEP_ALIVE_TIME = 1; // 保持存活时间 1秒
 
 private final BlockingQueue<Runnable> mWorkQueue = new LinkedBlockingQueue<>(128);
 
 private final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() {
 private final AtomicInteger mCount = new AtomicInteger(1);
 
 public Thread newThread(Runnable r) {
 Thread thread = new Thread(r, TAG + " #" + mCount.getAndIncrement());
 thread.setPriority(Thread.NORM_PRIORITY);
 return thread;
 }
 };
 
 private ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME,
 TimeUnit.SECONDS, mWorkQueue, DEFAULT_THREAD_FACTORY,
 new ThreadPoolExecutor.DiscardOldestPolicy());
 
 private static volatile ThreadPoolManager mInstance = new ThreadPoolManager();
 
 public static ThreadPoolManager getInstance() {
 return mInstance;
 }
 
 public void addTask(Runnable runnable) {
 mExecutor.execute(runnable);
 }
 
 @Deprecated
 public void shutdownNow() {
 mExecutor.shutdownNow();
 }
}


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

欢迎 发表评论:

最近发表
标签列表