如何理解线程池

池化技术

程序运行的本质是占用系统的资源。在多线程的环境下,每次创建和释放线程都十分的消耗资源。

如果我们使用一个容器来管理线程,让线程空闲时不释放,而是放到容器里,需要使用到线程时不用去创建,而是从容器中拿去,这样就大大减少了资源的消耗。这就是池化技术的概念。

池化技术应用非常广泛,常见的还有:jdbc 连接池,内存池,对象池,常量池……

池化技术的好处:

  1. 降低资源的消耗
  2. 提高响应的速度(没有创建和销毁过程)
  3. 方便管理。(线程复用,控制最大并发数,管理线程使用)

池化技术的容器

池化技术的容器一般都选择队列。因为队列有先进先出(FIFO)的特性。

而线程池具有并发的限制,故要选择阻塞队列 BlockingQueue。

BlockingQueue 下有 ArrayBlockingQueue,LinkedBlockQueue,LinkedBlockingDeque(双向),ConcurrentLinkedQueue 等。

1. ArrayBlockingQueue

ArrayBlockingQueue 基于数组实现(循环队列)。其通过 ReetrantLock 和 Condition 保证并发的安全性。

其有两个成员,putIndex(队尾),takeIndex(对头)。

putIndex 就是执行数组中上一个添加完元素的位置的下一个地方,如果 putIndex == length-1,则会以 0 开始。这就是循环队列的思想。

2. LinkedBlockingQueue

LinkedBlockingQueue 是基于链表实现,并且使用分离锁的技术来实现并发下的安全。

使用两个锁 takeLock 和 putLock,并且各自分离,互不影响。

当添加元素时,takeLock 会先锁住,然后再添加元素,并且去判断当前队列是否已满,如果满了则会 wait() 当前线程,即一直不释放锁,而其他添加元素的线程就只能一直等待。

当拿出元素时,takeLock 会先锁住,然后将元素拿出,并且判断当前是否队空,如果队空的话会 wait() 阻塞当前线程,使其他拿出元素的线程等待。添加元素的线程会去唤醒这个拿出元素的等待线程。同理,这个拿出元素的线程也会去唤醒添加元素的等待线程。

为什么能够让这两个锁各自分离且互不影响?

因为可以看作两个线程维护的是两个链表,并且当两个线程拿到同一个节点时,此时为队空,即会停止掉一个线程的并发,其他情况下,两个线程拿到的都不是同一个节点。

SynchronousQueue

SynchronousQueue 也是一个队列,但它的特别之处在于它内部没有容器。一个生产线程,当它生产成品后,如果没有人要消费,那么生产的线程也必须阻塞,不能继续生产。(只有一个容量的队列)。

队列的方法

功能抛出异常不跑出异常(返回值)阻塞等待超时等待
添加addofferputoffer
移除removepolltakepoll
判断队首elementpeek--
  • 抛出异常:当队空或队满进行拿出或添加时会抛出异常。
  • 返回值:当队空或队满进行拿出或添加时不是通过抛出异常来反馈,而是通过返回 true 或 false 来反馈。
  • 阻塞等待:若队空或队满时不能拿出或添加时线程会继续等待,直到能操作位置。
  • 超时等待:和阻塞等待类似,但有时间限制,如果超出时间,则会自动退出等待。

线程池分类

1. newFixedThreadPool(corePoolSize)

初始化一个指定线程数的线程池,使用 LinkedBlockingQueue 作为阻塞队列。

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

2. newCachedThreadPool()

初始化一个可以缓存线程的线程池,默认缓存时间是 60s,线程池的线程数可达到 Integer.MAX_VALUE,使用 SynchronousQueue
作为阻塞队列。

特点:在没有任务执行时,当线程的空闲时间超过 keepAliveTime,会自动释放线程资源;当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。

因此,使用时要注意控制并发的任务数,防止因创建大量的线程导致性能下降。

3. newSingleThreadExecutor()

初始化只有一个线程的线程池,使用 LinkedBlockingQueue 作为阻塞队列。

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

4. newScheduledThreadPool()

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

线程池的底层实现类

上述的线程池都是 jdk 帮我们封装好的。我们可以通过调用 Executors 类来创建上述线程池。

但阿里公约:线程池不允许使用 Excutors 去创建,而是通过底层类 ThreadPoolExecutor 的方式。这样的处理方式可以让开发人员更加明确线程池的运行规则,规避资源耗尽的风险。

上述的线程池也都是用 ThreadPoolExecuotr 来创建。

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

---

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

---

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

ThreadPoolExecutor 7 大参数

  1. corePoolSize:线程池初始的容量。
  2. maximumPoolSize:线程池最大容量。
  3. keepAliveTime:线程空闲时存活的时间。
  4. unit:keepAliveTime 的时间单位。
  5. workQueue:用来存放线程的阻塞队列。
  6. handler:拒绝策略(当队列满时继续添加时的策略)
    • AbortPolicy:直接抛出异常,默认策略。
    • CallerRunsPolicy:用调用者所在的线程执行任务。
    • DiscardOldestPolicy:丢弃阻塞队列中靠前的任务,执行当前任务。
    • DiscardPolicy:直接丢弃任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慢慢编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值