山西网站开发公司,做简历比较好的网站叫什么,机械行业营销型网站,西安微动免费做网站线程池技术
一、什么是线程池
线程池顾名思义是管理一组线程的池子。当有任务要处理时#xff0c;直接从线程池中获取线程来处理#xff0c;处理完之后线程不会立即销毁#xff0c;而是等待下一个任务。
二、为什么要使用线程池? 线程池的作用#xff1f;
1、降低资源…线程池技术
一、什么是线程池
线程池顾名思义是管理一组线程的池子。当有任务要处理时直接从线程池中获取线程来处理处理完之后线程不会立即销毁而是等待下一个任务。
二、为什么要使用线程池? 线程池的作用
1、降低资源消耗通过重复利用已创建的线程降低线程创建和销毁造成的消耗。2、提高响应速度当任务到达时任务可以不需要等待线程的创建就立即执行。3、提高了线程的可管理性通过创建线程池可以对线程进行统一的分配、调优和监控。
三、如何创建线程池
方法一通过ThreadPoolExecutor构造函数来创建线程池推荐。
public class TaskExecutor {//线程池核心线程数量public static final int CORE_POOL_SIZE 10;//线程池最大线程数public static final int MAX_POOL_SIZE 30;//当线程数大于核心线程数时多余的空闲线程存活的最长时间public static final int KEEP_ALIVE_TIME 100;//线程池等待队列public static final int BLOCKING_QUEUE_SIZE 10000;//通过ThreadPoolExecutor构造函数来创建线程池public static final ThreadPoolExecutor PROCESS_EXECUTOR new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.MICROSECONDS,new LinkedBlockingDequeRunnable(BLOCKING_QUEUE_SIZE),new ThreadFactoryBuilder().setNameFormat(thread-pool-%d).build());public static void executor(Runnable task) {PROCESS_EXECUTOR.execute(task);}
}方法二通过Executor框架的工具类Executors来创建线程池
public class TaskExecuter {public static void main(String[] args) {//通过Executors工具类来创建线程池ExecutorService threadPool Executors.newFixedThreadPool(10);threadPool.execute(() - {System.out.println(Hello wys);});}}问题思考 在实际的开发中为什么推荐使用通过ThreadPoolExecutor构造函数的方式来创建线程池而不推荐使用Executors工具类的方式来创建线程池呢 答 通过 ThreadPoolExecutor 构造函数的方式这样的处理方式让写的同学更加明确线程池的运行规则规避资源耗尽的风险因为我们通过使用ThreadPoolExecutor构造函数的方式去创建线程的话可以指定线程池的核心参数如线程池核心线程的数量、线程池最大线程的数量、空闲线程的存活时间、任务队列、拒绝策略等这样便于对线程池更好的统一的去管理避免了资源耗尽的风险。
另外Executors 返回线程池对象的弊端如下
FixedThreadPool 和 SingleThreadExecutor使用的是无界的 LinkedBlockingQueue任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求从而导致 OOM。CachedThreadPool使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE 可能会创建大量线程从而导致 OOM。ScheduledThreadPool 和 SingleThreadScheduledExecutor : 使用的无界的延迟阻塞队列DelayedWorkQueue任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求从而导致 OOM。 四、线程池核心参数
我们通过查看ThreadPoolExecutor构造函数源码来了解下线程池核心参数 public ThreadPoolExecutor(int corePoolSize, //线程池核心线程的数量int maximumPoolSize, //线程池最大线程数量long keepAliveTime, //当线程数量大于核心线程数量时多余的空闲线程存活的最长时间TimeUnit unit, //时间单位BlockingQueueRunnable 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.acc System.getSecurityManager() null ?null :AccessController.getContext();this.corePoolSize corePoolSize;this.maximumPoolSize maximumPoolSize;this.workQueue workQueue;this.keepAliveTime unit.toNanos(keepAliveTime);this.threadFactory threadFactory;this.handler handler;}ThreadPoolExecutor3个重要参数
corePoolSize : 核心线程的数量。任务队列未达到队列容量时最大可以同时运行的线程数量。maximumPoolSize : 最大线程数量。 任务队列已满时任务队列中存放的任务达到队列容量的时候当前可以同时运行的线程数量变为最大线程数。workQueue任务队列。新任务来的时候会先判断当前运行的线程数量是否达到核心线程数如果达到的话新任务就会被存放在队列中。
其他常见参数
keepAliveTime线程池中的线程数量大于 corePoolSize 的时候如果这时没有新的任务提交核心线程外的线程不会立即销毁而是会等待直到等待的时间超过了 keepAliveTime才会被回收销毁unit : keepAliveTime 参数的时间单位。threadFactory:executor创建新线程时候会用到。handler饱和策略。
五、线程的饱和策略
线程的饱和策略handler如果当前同时允许的线程数量达到最大线程数量并且任务队列也已经放满了任务时线程池的饱和策略就会生效ThreadPoolExecutor定义了一些策略
ThreadPoolExecutor.AbortPolicy丢弃新任务并抛出异常 RejectedExecutionException。AbortPolicy也是线程池默认的拒绝策略。ThreadPoolExecutor.CallerRunsPolicy它不会抛弃任务也不会抛出异常而是将任务回退给调用者的线程来执行ThreadPoolExecutor.DiscardPolicy不处理新任务直接丢弃。ThreadPoolExecutor.DiscardOldestPolicy丢弃当前任务队列中存在最久的任务提交新的任务并执行。 六、线程池常用的阻塞队列有哪些 ArrayBlockingQueue是基于数组实现的阻塞队列在初始化已经开辟好空间容量是固定的会存在内存空间浪费和内存碎片的问题 LinkedBlockingQueue无界队列是基于链表实现的阻塞队列当创建LinkedBlockingQueue时可以指定队列容量也可以不指定队列容量如果指定了队列容量则为有界队列如果未指定队列容量则默认为无界队列。如果为无界队列由于队列永远不会被放满容量为Integer.MAX_VALUE所以此时最多只能创建核心线程数的线程。 SynchronousQueue同步队列:是一种没有容量的队列每个插入操作一定要等待一个相应的删除操作即任务放进队列后被取出后才能继续放入目的是保证对于提交的任务如果有空闲线程则使用空闲线程来处理否则新建一个线程来处理任务。 DelayedWorkQueue延迟阻塞队列其内部元素并不是按照放入的时间排序而是会按照延迟的时间长短对任务进行排序内部采用“堆”的数据结构可以保证每次出对的任务都是当前队列中执行时间最靠前的。
七、线程池处理任务的流程
我们来介绍下线程池处理任务的流程即线程池执行execute的流程
1、如果当前运行的线程数小于核心线程数那么当有新的任务到来后会新建一个线程来执行任务懒加载核心线程2、如果当前运行的线程数等于或大于核心线程数并且线程状态为运行态那么就把此任务放到任务队列等待执行3、如果向任务队列投放任务失败任务队列已经满了但是当前运行线程数是小于最大线程数的那么就尝试创建非核心线程来执行任务。4、如果当前运行的线程数已经等于最大线程新建线程会使当前运行的线程超出最大线程数此时会调用饱和策略RejectedHandler拒绝当前任务。
八、如何给线程池命名
利用ThreadFactorBuilder来给线程池命名。即new ThreadFactorBuilder().setNameFormat().build();
ThreadFactory 接口是 Java 提供的一个创建线程的工厂接口我们可以通过实现这个接口来定制线程的行为。ThreadFactory 接口里唯一的方法是 newThread(Runnable r)它会创建并返回一个新的线程对象。 在实现该接口时可以通过重载 newThread() 方法来定制线程的名称。具体实现方式示例代码如下
ThreadFactory namedThreadFactory new ThreadFactoryBuilder().setNameFormat(my-thread-pool-%d).build();
ExecutorService executorService new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable(), namedThreadFactory);九、如何设定线程池的大小
一背景
我们在上面的学习中了解到使用线程池技术可以提高任务的响应速度那是不是就说明线程池中线程的数量越大越好呢其实理性的人都知道并不是这样的虽然我们可以将线程池的数量设定的很大但处理任务的CPU资源是不变的这样就会导致大量的上下文切换从而增加线程的执行时间影响了整体执行效率。
二上下文切换
问题1什么是上下文切换 1、上下文切换是指在多线程环境下CPU从一个线程切换到另一个线程时保存当前线程的上下文信息并加载另一个线程上下文信息的过程。 而上下文信息指线程在执行过程中自己的运行条件和状态比如程序计数器、栈信息、寄存器的值等寄存器是指CPU内部一组高速存储单元用于临时存储和操作数据
2、多线程编程中一般线程的个数都大于 CPU 核的个数而一个 CPU 核心在任意时刻只能被一个线程使用为了让这些线程都能得到有效执行CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用这个过程就属于一次上下文切换。概括来说就是当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态以便下次再切换回这个任务时可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。上下文切换通常是计算密集型的。也就是说它需要相当可观的处理器时间在每秒几十上百次的切换中每次切换都需要纳秒量级的时间。所以上下文切换对系统来说意味着消耗大量的 CPU 时间事实上可能是操作系统中时间消耗最大的操作。
三如何设定线程池的大小
CPU 密集型任务(线程池设定大小N1) 这种任务消耗的主要是 CPU 资源可以将线程数设置为 NCPU 核心数1。比 CPU核心数多出来的一个线程是为了防止线程偶发的缺页中断或者其它原因导致的任务暂停而带来的影响。一旦任务暂停CPU 就会处于空闲状态而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。I/O 密集型任务(线程池设定2N或是更大) 这种任务应用起来系统会用大部分的时间来处理 I/O 交互即CPU大部分时间是空闲而线程在处理 I/O 的时间段内不会占用 CPU 来处理这时就可以将 CPU 交出给其它线程使用。因此在 I/O密集型任务的应用中我们可以多配置一些线程具体的计算方法是 2N。