多线程之线程池

简介: 多线程之线程池

什么是线程池

在真实的生产环境中,可能需要很多的线程来支撑整个应用,当线程数量非常多时,反而会耗尽CPU资源,如果不对线程进行控制和管理,反而会影响程序的性能,线程开销主要包括:

  • 创建与启动线程的开销
  • 线程销毁的开销
  • 线程调度的开销
  • 线程数量受限CPU处理器数量

线程池就是有限使用线程的一种常用方式,线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池,线程池将这些任务缓存在工作队列中,线程池中的工作线程不断地从队列中取出任务并执行。

JDK提供与线程池相关的API

JDK提供了一套Executor框架,可以帮助开发人员有效使用线程

一般使用ExecutorService的实体类Executors的实例方法来创建线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoor {
    public static void main(String[] args) {
        //创建有五个大小的线程池
        ExecutorService fixedThreadPool =Executors.newFixedThreadPool(5);
        //向线程池提交18个任务
        for (int i = 0; i <18 ; i++) {
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getId()+"编号任务正在执行");
                    try {
                        Thread.sleep(2000);//模拟任务时长
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

向线程池提交了18个任务,这18个任务都存储在阻塞队列,线程池中5个线程在阻塞队列中取5个任务执行。

线程池的计划任务

ExecutorService还有一个子接口ScheduledExecutorService,这一个线程池可以对任务进行调度,有计划的执行某个任务。

package com;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceText {
    public static void main(String[] args) {
        //创建一个有调度功能的线程池
        ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(10);
        //在延迟两秒之后执行任务
        scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId()+"--"+System.currentTimeMillis());
            }
        },2, TimeUnit.SECONDS);
        //以固定的频率执行任务 3秒以后执行 每隔2秒执行一次
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId()+"以固定频率开启任务"+System.currentTimeMillis());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },3,2,TimeUnit.SECONDS);
        //在上次任务结束之后,固定延迟再次执行该任务,不管任务执行多长,总是在任务结束后2秒再次执行
        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId()+"在固定频率开启任务"+System.currentTimeMillis());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },3,2,TimeUnit.SECONDS);
    }
}

  • scheduled(Runnable任务,延迟时长,时间单位)给某一个线程固定的延迟
  • scheduleAtFixedRate(Runnable任务,延迟时长,固定频率,时间单位),如果睡眠时间超过任务等待时间,完成任务后立即执行下一个任务,如果任务等待一天,你执行耗时1.5天,超过了任务等待的时间,下一次开启不需要又等待一天,直接立即执行。
  • scheduleWithFixedDelay(Runnable任务,延迟时长,固定频率,时间单位),固定延迟再次执行该任务,不管任务执行多长,总是在任务结束后再次执行,如果任务等待一天,你执行耗时1.5天,任务结束后你还需要等待1.5天才能执行。

线程池的底层实现

查看Excutors工具类中newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPool源码

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
  //核心线程和最大线程相同,如果多出一个会放在阻塞队列中
    }
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    //这个线程核心线程和最大线程数都为1,在任意时刻只有一个线程在执行任务,如果有多个任务放在阻塞队列中
    }
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
       //在极端情况下,每次提交新的任务都会创建新的线程执行,适合用来执行大量耗时短并且提交频繁的任务
    }

Excutors工具类中返回线程池的方法底层都使用了ThreadPoolExecutor线程池,这些方法都是ThreadPoolExecutor线程池的封装,ThreadPoolExecutor是ExecutorService的实现类。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

各个参数的含义:

  • corePoolSize线程池中核心线程的数量
  • maximumPoolSize线程池中最大线程数量
  • keepAliveTime当线程池线程的数量超过corePoolSize时,多余的空闲线程的存活时长,即空闲线程在多少时间内销毁
  • unit实参单位
  • workQueue任务队列,把任务提交到该任务队列中等待执行
  • threadFactory线程工程用于创建线程
  • handler拒绝策略,当任务太多来不及处理时,如何拒绝

说明:workQueue工作队列是指提交未执行的任务队列,它是BlockingQueue接口的对象,仅用于存储Runnable接口。根据队列功能分类,在ThreadPoorExecutor构造方法可以使用以下几种阻塞队列:

  • 直接提交队列,由SynchronousQueue对象提供,该队列没有容量,提交给线程池的任务不会被真是保存,总是将新的任务提交给线程执行,如果没有空闲线程则尝试创建新的线程,如果线程数量已经达到maxinumpoolsize规定的最大值则执行拒绝策略。
  • 有界任务队列,由ArrayBlockingQueue实现,在创建ArrayBlockQueue对象时,可以指定一个容量,当有任务需要执行时,如果线程池种中线程数量小于CorePoolSize核心线程数则创建新的线程,如果大于核心线程数则加入等待队列,如果队列已满则无法加入,在线程数小于maxinumpoo;size指定的最大线程数前提下会创建新的线程来执行,如果线程数大于maxinumpoolsize最大线程数则执行绝策略
  • 无界任务队列,由LinkBlockQueue对象实现,与有界队列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况,当有新的任务时,在系统线程数小于corepoolsize核心线程数,核心线程数则把任务加入阻塞队列
  • 优先任务队列,由priorityBlockQueue实现的,是带有任务优先级的队列,是一个特殊的无界队列,不管是ArrayBlockQueue队列还是LinkedBlockQueue队列都是按照先进先出算法处理的,在priorityBlockQueue队列中可以根据任务优先级顺序先后执行

线程池的拒绝策略

ThreadPoolExecutor方法的最后一个参数指定了拒绝策略,当提交给线程池任务量超过承载能力,即线程用完了,等待队列也满了,无法为新的提交任务服务,可以通过拒绝策略来解决问题,JDK提供了四种拒绝策略。

线程池中定义了四个内部类都实现了拒绝策略继承了RejectedExecutionHandler接口

AbortPolicy策略会抛出异常

CallerRunsPolicy策略,只要线程池没有关闭,会调用线程中运行当前被丢弃的任务

DiscardPolicy策略,直接丢弃这个无法处理的任务

DiscardOldestPolicy策略,会将任务队列中最老的任务丢弃,尝试再次提交新任务

defaultHandler是默认的拒绝策略,AbortPolicy抛出异常

private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();

如果内置拒绝策略午饭满足实际需求,可以扩展RejectedExecutionHandler接口


目录
打赏
0
0
0
0
19
分享
相关文章
|
24天前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
155 60
【Java并发】【线程池】带你从0-1入门线程池
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
1月前
|
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
64 20
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
295 64
|
3月前
|
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
94 1
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
153 38
|
4月前
|
.如何根据 CPU 核心数设计线程池线程数量
IO 密集型:核心数*2 计算密集型: 核心数+1 为什么加 1?即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。
214 4
|
4月前
|
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
208 2
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
752 2

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等