Java线程池,最详细!!!

1.线程池

1.1  什么是线程池


线程池是一种管理线程的技术,它允许开发者预先创建一定数量的线程,并将这些线程放入一个容器中,以等待任务被分配
。‌

简言之:线程池:用于管理线程对象的池子。

1.2  线程的主要作用

  • 管理和复用线程。线程池可以维护多个线程,避免每次需要执行任务时都创建新线程,从而减少线程的创建和销毁开销,提高系统性能。
  • 提高系统资源利用率。通过复用已存在的线程,线程池可以避免因频繁创建和销毁线程而导致的系统资源浪费。
  • 优化性能。在执行大量异步任务时,线程池能够显著提高系统的吞吐量。‌
  • 提供灵活性。线程池允许开发者更方便地控制线程的最大并发数、线程的定时任务、单线程的顺序执行等。

1.3  为什么使用线程 

  • 资源节约:避免了线程创建与销毁的高成本,通过复用线程减少内存和操作系统资源的消耗。

  • 快速响应:预创建的线程能即时处理新任务,无需等待线程初始化,加快应用响应速度。

  • 管理简化:集中管理线程生命周期,便于调整和监控线程数,防止资源过度消耗,增强系统稳定性和可控性,解决大量创建线程而导致的内存泄露问题。

 1.4  如何创建线程池

java提供了两种方式:

  1. 通过工具类(Executors) 完成线程池的创建,语法简单,但是阿里巴巴不建议使用
  2. 通过线程池类:ThreadPoolExecutor类,语法复杂,但阿里巴巴建议使用,特点灵活

以下是不推荐使用Executors类创建线程池的原因:

  1. 资源耗尽风险:使用Executors类创建的线程池,如FixedThreadPool和SingleThreadPool,可能会导致请求队列堆积大量任务,最终引发内存溢出(OOM)的风险。
  2. 不可控制的线程数:例如CachedThreadPool会无限创建新线程,直到达到系统资源的极限,这可能导致系统负载过高。
  3. 缺乏灵活性:Executors类提供的线程池通常具有固定的参数配置,这限制了对于线程池参数的自定义调整,如核心线程数、最大线程数、任务队列等。
  4. 不够透明:使用Executors类创建的线程池可能不够透明,无法清晰地了解其内部工作机制和潜在问题,这对于性能调优和故障排查都是不利的。

 1.4.1   Executors

  1. 固定大小的线程池对象:newFixedThreadPool
  2. 单一线程池:newSingleThreadExecutor
  3. 可变线程池:newCachedThreadPool
  4. 延迟线程池:new ScheduledThreadPool

 固定大小的线程池对象:newFixedThreadPool

public class Test01 {
    public static void main(String[] args) {
        /*  线程的根接口为:Executor:里面只有一个execute方法
         *   子接口: ExecutorService:里面常用的方法:submit()方法      shutdown()方法:关闭线程池,如果里面有为执行完的任务,需要等任务执行完毕才会关闭
         *          shutdownNow():立即关闭,线程池中的任务执行完毕后关闭,不出伦理等待队列中的任务
         *           isTerminated():判断是否关闭了
         */
        //创建一个固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //执行线程任务
        for (int i = 0; i < 6; i++) {
            /*
             * 1.必须传递Runnable方法
             *     [1] 自己创建一个类实现Runnable接口
             *     [2]  匿名内部类
             *     [3]  lambda表达式:前提接口必须为函数接口
             * */
            //自己创建一个类实现Runnable接口
            //executorService.execute(new my());
            //匿名内部类
            /*executorService.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"==============");
                }
            });*/
            //lambda表达式:前提接口必须为函数接口
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"==============");
            });
        }
    }
}

class my implements Runnable {

    public void run() {
        System.out.println(Thread.currentThread().getName()+"============");
    }
}

 

 单一线程池:newSingleThreadExecutor

public class Test02 {
    public static void main(String[] args) {
        //创建单一线程池,[池子里面只有一个线程对象] 适合任务顺序执行的
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i=0;i<5;i++){
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"========");
            });
        }
    }
}

可变线程池:newCachedThreadPool 

public class Test03 {
    public static void main(String[] args) {
        //创建可变线程池[池子中线程的数量会随着任务的大小二变化] 
        // 数量多的情况下不会看到相同数量的线程,因为有空闲线程
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i=0;i<10;i++){
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+"============");
            });
        }
    }
}

延迟线程池:new ScheduledThreadPool

public class Test04 {
    public static void main(String[] args) {
        //延迟线程池;[指定时间后执行] 括号里面的值:线程数
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        //表示十秒后执行任务代码块
        scheduledExecutorService.schedule(() -> {
            System.out.println(Thread.currentThread().getName() + "-----------------");
        }, 2, TimeUnit.SECONDS);
        //表示五秒后执行代码块,每两秒再执行一次
        scheduledExecutorService.scheduleAtFixedRate(()->{
            System.out.println(Thread.currentThread().getName()+"=====");
        },5,2,TimeUnit.SECONDS);
        //关闭线程池
        
    }
}

 execute和submit方法的区别

这两个方法都是用来执行线程任务,但是execute属于Executor类中的方法,而submit属于ExecutorService接口中的方法。 而且submit可以执行runnable和callable类型的任务,而execute只能执行Runnable类型的任务。 submit执行完任务后有返回结果。

 1.4.2     ThreadPoolExecutor  

public class Test05 {
    public static void main(String[] args) {
        /*int corePollSize 核心线程数的个数  2
         * int maximumPoolSize 最大线程数量 6
         * long keepAliveTime 非核心线程允许空闲的时间 10
         * TimeUnit.SECONDS  时间单位
         * ArrayBlockingQueue 堵塞中的队列 5*/
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(5);
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 6, 10, TimeUnit.SECONDS, blockingQueue);
        for (int i = 0; i < 10; i++) {
            poolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "==============");
            });
        }
    }
}

如果提交的任务超过线程池处理速度,会抛出RejectedExecutionException异常

我们可以通过下面的场景理解ThreadPoolExecutor中的各个参数;
a客户(任务)去银行(线程池)办理业务,但银行刚开始营业,窗口服务员还未就位(相当于线程池中初始线程数量为0),
于是经理(线程池管理者)就安排1号工作人员(创建1号线程执行任务)接待a客户(创建线程);
在a客户业务还没办完时,b客户(任务)又来了,于是经理(线程池管理者)就安排2号工作人员(创建2号线程执行任务)接待b客户(又创建了一个新的线程);假设该银行总共就2个窗口(核心线程数量是2);
紧接着在a,b客户都没有结束的情况下c客户来了,于是经理(线程池管理者)就安排c客户先坐到银行大厅的座位上(空位相当于是任务队列)等候,
并告知他: 如果1、2号工作人员空出,c就可以前去办理业务;
此时d客户又到了银行,(工作人员都在忙,大厅座位也满了)于是经理赶紧安排临时工(新创建的线程)在大堂站着,手持pad设备给d客户办理业务;
假如前面的业务都没有结束的时候e客户又来了,此时正式工作人员都上了,临时工也上了,座位也满了(临时工加正式员工的总数量就是最大线程数),
于是经理只能按《超出银行最大接待能力处理办法》(饱和处理机制)拒接接待e客户;
最后,进来办业务的人少了,大厅的临时工空闲时间也超过了1个小时(最大空闲时间),经理就会让这部分空闲的员工人下班.(销毁线程)
但是为了保证银行银行正常工作(有一个allowCoreThreadTimeout变量控制是否允许销毁核心线程,默认false),即使正式工闲着,也不得提前下班,所以1、2号工作人员继续待着(池内保持核心线程数量); 

 

  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值