JUC并发编程——线程池(上)

简介: JUC并发编程——线程池

正文


一、线程池优点


1、降低资源消耗:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳定性,通过重复利用已创建的线程可以降低线程的创建和销毁造成的消耗。


2、提高响应速度:任务到达时,无需等待线程创建即可立即执行。


3、提高线程的可管理性:线程池提供了一种限制、管理资源的策略,维护一些基本的线程统计信息,如已完成任务量等,通过线程池可以对线程资源进行资源统一分配、监控和调优。


二、线程池原理


创建线程池的方式


Executors.newCachedThreadPool(); 可缓存线程池

Executors.newFixedThreadPool();固定数量的线程池

Executors.newScheduledThreadPool() ; 定时执行的线程池

Executors.newSingleThreadExecutor(); 单线程线程池

线程池的创建有四种方式,但是底层都是使用了ThreadPoolExecutor构造函数。


public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }


ThreadPoolExecutor参数说明


corePoolSize--------------核心线程数,必须大于等于0。


maximumPoolSize--------------最大线程数,必须大于核心线程数。


keepAliveTime---------------------非核心线程存活的时间,当非核心线程在空闲时间超过这个值之后,会关闭非核心线程。


TimeUnit------------------------------时间单位。纳秒,微秒,毫秒,秒,分,小时,天TimeUnit.NANOSECONDS、TimeUnit.MICROSECONDS、TimeUnit.MILLISECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES、TimeUnit.HOURS、TimeUnit.DAYS


BlockingQueue<Runnable> workQueue--------------存放线程的阻塞队列。


RejectedExecutionHandler handler----------------------线程池拒绝策略的处理类


队列说明


1.ArrayBlockingQueue:有界队列,基于数组结构,按照队列FIFO原则对元素排序;

2.LinkedBlockingQueue:无界队列,基于链表结构,按照队列FIFO原则对元素排序,Executors.newFixedThreadPool()使用了这个队列; 无界默认是Integer.MAX_VALUE,有界则是 可以自己定义。

3.SynchronousQueue:同步队列,该队列不存储元素,每个插入操作必须等待另一个线程调用移除操作,否则插入操作会一直被阻塞,Executors.newCachedThreadPool()使用了这个队列;

4.PriorityBlockingQueue:优先级队列,具有优先级的无限阻塞队列。


以ArrayBlockingQueue为例如下


package com.xiaojie.juc.thread.pool;
import java.util.concurrent.*;
/**
 * @author xiaojie
 * @version 1.0
 * @description: 缓存功能的线程池
 * @date 2021/12/12 20:31
 */
public class CachedThreadPoolDemo {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<String>(5, true);
        arrayBlockingQueue.offer("a");
        arrayBlockingQueue.offer("b");
        arrayBlockingQueue.offer("c");
        arrayBlockingQueue.offer("d");
        arrayBlockingQueue.offer("e");
        System.out.println(arrayBlockingQueue.size());
        System.out.println(arrayBlockingQueue.poll());//从队列中取值之后,删除数据
        System.out.println(arrayBlockingQueue.size());
        System.out.println(arrayBlockingQueue.peek());//取值后不删除数据
        System.out.println(arrayBlockingQueue.size());
    }
}

线程池的执行流程



如果当前工作线程数小于核心线程数,执行器总是优先创建一个新的线程,而不是从线程队列中获取一个空闲线程。

如果线程池中的总任务数量大于核心线程数量,新接手的任务将会存入阻塞队列,一直到阻塞队列满为止。在核心线程已用完,而阻塞队列未满的情况下线程池不会创建新线程,而是复用核心线程。

当完成一个任务时,执行器优先从阻塞队列中获取下一个任务开始执行,一直到阻塞队列为空,其中所有的缓存任务被取光。

在核心线程已经用完并且阻塞队列也已经满了的情况下,如果线程池接收新的任务,将会为新任务创建一个新的线程(非核心线程),并且会立即执行新任务。

在核心线程用完,阻塞队列已满,一直会创建新的线程直到线程池中的线程总数超过最大线程数。如果超过最大线程数,线程池就会拒绝接收新任务,当新任务到来时,执行拒绝策略。


线程池拒绝策略


两种情况会拒绝处理任务:

1.当线程数已经达到maxPoolSize,并且队列已满,会拒绝新任务。

2.当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。


线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认是AbortPolicy,会抛出异常。

ThreadPoolExecutor类有几个内部实现类来处理拒绝任务:


1.AbortPolicy 丢弃任务,抛运行时异常

2.CallerRunsPolicy 执行任务

3.DiscardPolicy 忽视,什么都不会发生

4.DiscardOldestPolicy 从队列中踢出最先进入队列的任务

5.实现RejectedExecutionHandler接口,可自定义处理器


相关文章
|
4月前
|
Java 程序员 调度
【JAVA 并发秘籍】进程、线程、协程:揭秘并发编程的终极武器!
【8月更文挑战第25天】本文以问答形式深入探讨了并发编程中的核心概念——进程、线程与协程,并详细介绍了它们在Java中的应用。文章不仅解释了每个概念的基本原理及其差异,还提供了实用的示例代码,帮助读者理解如何在Java环境中实现这些并发机制。无论你是希望提高编程技能的专业开发者,还是准备技术面试的求职者,都能从本文获得有价值的见解。
67 1
|
19天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
2月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
31 3
|
2月前
|
Java C++
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
34 0
|
3月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
3月前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
164 6
【Java学习】多线程&JUC万字超详解
|
3月前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
61 3
|
3月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
3月前
|
并行计算 API 调度
探索Python中的并发编程:线程与进程的对比分析
【9月更文挑战第21天】本文深入探讨了Python中并发编程的核心概念,通过直观的代码示例和清晰的逻辑推理,引导读者理解线程与进程在解决并发问题时的不同应用场景。我们将从基础理论出发,逐步过渡到实际案例分析,旨在揭示Python并发模型的内在机制,并比较它们在执行效率、资源占用和适用场景方面的差异。文章不仅适合初学者构建并发编程的基础认识,同时也为有经验的开发者提供深度思考的视角。
|
4月前
|
Java 数据库连接 微服务
揭秘微服务架构下的数据魔方:Hibernate如何玩转分布式持久化,实现秒级响应的秘密武器?
【8月更文挑战第31天】微服务架构通过将系统拆分成独立服务,提升了可维护性和扩展性,但也带来了数据一致性和事务管理等挑战。Hibernate 作为强大的 ORM 工具,在微服务中发挥关键作用,通过二级缓存和分布式事务支持,简化了对象关系映射,并提供了有效的持久化策略。其二级缓存机制减少数据库访问,提升性能;支持 JTA 保证跨服务事务一致性;乐观锁机制解决并发数据冲突。合理配置 Hibernate 可助力构建高效稳定的分布式系统。
70 0