多线程之——比线程更优的“线程池“ (经典面试题 ! !) [多线程系列--3]

🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇

                                 多线程之线程池                            

🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇

今日推荐歌曲: Psycho,Pt. 2(Explicit)   --Russ  🎵🎵


目录

前言

一. 线程池是什么(为什么要有线程池)

1.线程池是什么

2.为什么要有线程池

二. 标准库中的线程池 (ThreadPoolExecutor)

2. Executors 创建线程池的⼏种⽅式

三. ThreadPoolExecutor 参数  ⭐⭐⭐

3.1 线程工厂 (详解)

四. 自定义实现线程池

五. 总结


前言

这篇文章将详细介绍可以进一步优化多线程成本的线程池!


一. 线程池是什么(为什么要有线程池)

1.线程池是什么

学到这里,"池"这个概念在很多地方都用到,常量池,数据库,连接池,线程池,进程池,内存池.相信各位友友们应该也不陌生了

主要作用:.

1.提前把要用的对象准备好

2.把用完的对象也不要立即释放,先留着以备下次使用.提高效率!

线程池也是如此:

把要使用的线程提前创建好.用完了也不要直接释放而是以备下次使用.就节省了创建/销毁线程的开销. 在这个使用的过程中,并没有真的频繁创建销毁,而只是从线程池里,取线程使用,用完了还给线程池.

这里举个例子:

想象这么⼀个场景: 在学校附近新开了⼀家快递店,⽼板很精明,想到⼀个与众不同的办法来经营。店⾥没有雇⼈,⽽是 每次有业务来了,就现场找⼀名同学过来把快递送了,然后解雇同学。这个类⽐我们平时来⼀个任 务,起⼀个线程进⾏处理的模式。 很快⽼板发现问题来了,每次招聘+解雇同学的成本还是⾮常⾼的。⽼板还是很善于变通的,知道 了为什么⼤家都要雇⼈了,所以指定了⼀个指标,公司业务⼈员会扩张到3个⼈,但还是随着业务 逐步雇⼈。于是再有业务来了,⽼板就看,如果现在公司还没3个⼈,就雇⼀个⼈去送快递,否则 只是把业务放到⼀个本本上,等着3个快递⼈员空闲的时候去处理。这个就是我们要带出的线程池 的模式。


2.为什么要有线程池

最开始,进程,能够解决并发编程问题,因为频繁创建销毁进程,成本太高了.

引入了轻量级进程=>线程.如果创建销毁线程的频率进一步提高,此时线程的创建销毁开销,也就不能无视了!!

解决方案,有两种:

1.引入轻量级线程=>也称为纤程/协程  Java21里引入的“虚拟线程”就是这个东西

Go是比较早支持协程的(这个概念很多年前就有,但是真正集成到语言中,Go是比较早)Go也是凭借,语法简单,协程,就火了.协程本质,是程序猿在用户态代码中进行调度,不是靠内核的调度器调度的~~ 节省了很多的调度上的开销

2.第二个就是线程池了,线程池最⼤的好处就是减少每次启动、销毁线程的损耗。


二. 标准库中的线程池 (ThreadPoolExecutor)


ThreadPoolExecutor 本身用起来比较复杂.因此标准库还提供了另一个版本,把ThreadPoolExecutor 给封装了一下,就是 Executors 工厂类.通过这个类来创建出不同的线程池对象.(在内部把ThreadPoolExecutor 创建好了并且设置了不同的参数)

• 使⽤Executors.newFixedThreadPool(10)能创建出固定包含10个线程的线程池.

• 返回值类型为ExecutorService

• 通过ExecutorService.submit可以注册⼀个任务到线程池中.

 ExecutorService pool = Executors.newFixedThreadPool(10);
 pool.submit(new Runnable() {
 @Override
 public void run() {
    System.out.println("hello");
 }
 });

2. Executors 创建线程池的⼏种⽅式

newFixedThreadPool:创建固定线程数的线程池

• newCachedThreadPool:创建线程数⽬动态增⻓的线程池.

• newSingleThreadExecutor: 创建只包含单个线程的线程池.

• newScheduledThreadPool:设定延迟时间后执⾏命令,或者定期执⾏命令.是进阶版的Timer.(定时器)

Executors 本质上是ThreadPoolExecutor类的封装.


三. ThreadPoolExecutor 参数  ⭐⭐⭐

ThreadPoolExecutor 提供了更多的可选参数,可以进⼀步细化线程池⾏为的设定.

• corePoolSize: 核心线程数  正式员⼯的数量.(正式员⼯,⼀旦录⽤,永不辞退)

• maximumPoolSize: 最大线程数  正式员⼯+临时⼯的数⽬.(临时⼯:⼀段时间不⼲活,就被辞退).

• keepAliveTime: 保持存活时间  临时⼯允许的空闲时间.

• unit:keepaliveTime 的时间单位  是秒,分钟,还是其他值.

• workQueue: 线程任务队列  传递任务的阻塞队列 (也可以是阻塞队列 BlockingQueue )

• threadFactory: 创建线程的⼯⼚  参与具体的创建线程⼯作.通过不同线程⼯⼚创建出的线程相当于 对⼀些属性进⾏了不同的初始化设置.

• RejectedExecutionHandler: 拒绝策略(⭐⭐面试考点)  如果任务量超出公司的负荷了接下来怎么处理.     

   ◦ AbortPolicy(): 超过负荷,直接抛出异常

   ◦ CallerRunsPolicy():调⽤者负责处理多出来的任务.

   ◦ DiscardOldestPolicy():丢弃队列中最⽼的任务

 . ◦ DiscardPolicy():丢弃新来的任务.


3.1 线程工厂 (详解)

工厂模式,也是一种常见的设计模式, 通过专门的 "工厂类”/“工厂对象” 来创建指定的对象~~通过这个工厂类,来创建线程对象(Thread对象)

举个栗子:

本来应该是这两个方法构成重载,但这里明显不满足重载的要求.


四. 自定义实现线程池

• 核⼼操作为 submit ,将任务加⼊线程池中

• 使⽤ MyThreadPoolExecutor 类描述⼀个⼯作线程.使⽤Runnable描述⼀个任务.

• 使⽤⼀个 BlockingQueue 组织所有的任务

• 每个 t 线程要做的事情:不停的从 BlockingQueue 中取任务并执⾏.

• 指定⼀下线程池中的最⼤线程数 maxWorkerCount ;当当前线程数超过这个最⼤值时,就不再新增线程了.

   代码实现:

package Thread;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThreadPoolExecutor1{
    //• 核⼼操作为 submit ,将任务加⼊线程池中
//• 使⽤ ThreadTask 类描述⼀个⼯作线程.使⽤Runnable描述⼀个任务.
//• 使⽤⼀个 BlockingQueue 组织所有的任务
//• 每个 t 线程要做的事情:不停的从 BlockingQueue 中取任务并执⾏.
// • 指定⼀下线程池中的最⼤线程数 maxWorkerCount ;当当前线程数超过这个最⼤值时,就不再新增线程了.
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);

    //用来存放每个线程以便后续操作
    private List<Thread> ThreadList = new ArrayList<>();
    
    //构造方法:创建n个线程不断去执行队列中的任务
    public MyThreadPoolExecutor1(int n){
      for(int i =0;i<n;i++){
          Thread t = new Thread(()->{
              while(true){
                  try {
                      Runnable runnable = queue.take();
                      runnable.run();
                  } catch (InterruptedException e) {
                      throw new RuntimeException(e);
                  }
              }
          });
          t.start();
          ThreadList.add(t);
      }
    }

    //用来接收任务放到队列中
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}


public class ThreadDemo261 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor1 myThreadPoolExecutor1 = new MyThreadPoolExecutor1(10);
        for (int i = 0; i < 100; i++) {
            int n = i;
            myThreadPoolExecutor1.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务 " +n+" 当前执行线程:"+Thread.currentThread().getName());
                }
            });
        }
    }
}

五. 总结

这里详细叙述的线程池的作用以及实现方法,在找工作面试的时候会经常问到 ThreadPoolExecutor 的参数,大家要好好记住哦 ! ! !

真嘟很详细,手敲原创的 ,希望能帮助大伙,博客不易,点赞 收藏 加关注,知识进脑不迷路!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值