【JavaEE】——线程池大总结

简介: 线程数量问题解决方式,代码实现线程池,ThreadPoolExecutor(核心构造方法),参数的解释(面试:拒绝策略),Executors,工厂模式,工厂类

  image.gif 编辑

阿华代码,不是逆风,就是我疯,

你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!

目录

引入:问题引入

一:解决方案

1:方案一——协程/纤程

(1)本质

2:方案二——线程池

(1)本质

(2)优缺点

(3)解释高效的原因

二:ThreadPoolExecutor(标准库线程池)

1:Java库中找

2:构造方法

(1)核心线程数

(2)最大线程数

(3)保持存活时间

(4)时间单位

(5)工作任务

(6)线程工厂

①工厂模式

(7)拒绝策略(面试高频)

①中止策略

②甩锅策略

③喜新厌旧

④忠贞不渝

三:Executors(工厂类)

1:.newFixedThreadPool(可以设定固定线程数目)

2:submit添加任务

编辑3:线程池中线程数量问题

(1)前引

(2)线程任务的分类

①CPU密集型任务

②IO密集型任务

(3)分情况讨论

四:通过代码实现简单的线程池

1:思路

2:代码示例


引入:问题引入

在之前的学习中,我们了解到,为了降低频繁创建和销毁进程所带来的巨大开销,我们引入了轻量级进程的概念(线程)

现在若线程的数量进一步提升,那么线程的频繁创建和销毁所带来的资源消耗,我们也不能忽视了

所以我们进行优化,引入了“池”的概念:这里有许多种类的池,线程池,数据库连接池,进程池......(提前把需要用到的对象准备好,用完的 对象也不要直接扔掉,放到池子中以便下次使用)

一:解决方案

1:方案一——协程/纤程

注:可以理解为轻量级线程

(1)本质

通过用户态代码进行调度,不靠系统内核的调度器调度(节省了调度的开销)

注:在java21中“虚拟线程”就是这个意思。

      在用户代码中,协程是基于线程进行封装的。

      go是比较早支持协程的,因为语法简单就火了

2:方案二——线程池

(1)本质

提前创建好线程,需要用的时候直接从池子里拿出来用,用完了也不要释放而是返还回池子中。

(2)优缺点

①优点:节省了创建和销毁线程带来的资源消耗,更高效

②缺点:占用了内存空间

(3)解释高效的原因

从线程池里获取线程,是在用户态代码中进行调度,是可控的,高效的

从操作系统中获取线程,是在系统内核中进行完成的,不可控,低效。

image.gif 编辑

二:ThreadPoolExecutor(标准库线程池)

1:Java库中找

注:打开网站Overview (Java Platform SE 8 ),找到对应的包和class类

image.gif 编辑 image.gif 编辑

2:构造方法

image.gif 编辑

我们直接看带有7个参数的构造方法

(1)核心线程数

int corePoolSize

core(核心)pool(池)siz(大小)

(2)最大线程数

int maximumPoolSize

核心线程可以理解为公司的正式员工,不能轻易裁掉;

普通线程可以理解为公司的实习生,裁掉比较容易

最大线程数 =  核心线程数 +普通线程数

(3)保持存活时间

long keepAliveTime

(4)时间单位

TimeUnit unit

单位:s,min,hour.......

普通线程能空闲的最大时间,超过空闲时间限制,就会被移除线程池

还是用上述例子举例,实习生不能说开就开,假定摸鱼时间限制为1个小时,只要实习生摸鱼的时间小于1个小时就不会被开,超过就被开

(5)工作任务

BlockingQueue<Runnable>  workQueue

与定时器(上篇文章)相似,线程池可以持有多个任务

Runnable用来描述任务的主体

<>也可以写PriorityQueue优先级队列

(6)线程工厂

ThreadFactory threadFactory

①工厂模式

通过(“工厂类”)类里面的(不一定是静态的)方法,对方法内部的new对象进行构造,完成对象的初始化(相当于,给构造方法外面在套上一层方法——套娃“封装”)

image.gif 编辑

image.gif 编辑

(7)拒绝策略(面试高频)

RejectedExecutionHandler handler——

execution(执行)handler(操作者)

问题:试想,线程池中有一个阻塞队列,存放的线程数目已经达到最大荣达,这个时候还往里面存放,那么线程池会怎么办?

image.gif 编辑

①中止策略

.AbortPolicy ——

如果硬要在加新任务的话,线程池:我吃柠檬,lz新旧任务都不干了,抛出异常

image.gif 编辑

②甩锅策略

.CallerRunsPolicy——

线程池:让交代给我这个任务的人自己完成这个线程,我才不干

③喜新厌旧

.DiscardOldestPolicy——

discard(丢弃)

线程池抛弃池中呆的最久最老的一个线程,迎接新欢(喜新厌旧)

④忠贞不渝

.DiscardPolicy——

丢弃要新添加的任务,继续我行我素执行线程池中本来就有的任务

三:Executors(工厂类)

因为ThreadPoolExecutor使用起来较为复杂,所以标准库中就封装了一下,提供了Executors这个版本(工厂类,在内部把ThreadPoolExecutor创建好了,并且设置了不同的参数)

1:.newFixedThreadPool(可以设定固定线程数目)

image.gif 编辑

2:submit添加任务

package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadDemon34 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(4);
        service.submit(new Runnable(){
            @Override
            public void run() {
                System.out.println("执行线程池中第一个任务");
            }
        });
    }
}

image.gif

image.gif 编辑

返回值类型点进去

image.gif 编辑3:线程池中线程数量问题

(1)前引

我们知道,线程的运行效率,跟cpu的逻辑核心数直接相关,假设cpu的逻辑核心数为N,那线程的数量该是多少合适(2N?1.5N?N?..........)

(2)线程任务的分类

①CPU密集型任务

线程大部分时间都在CPU上运行,计算

②IO密集型任务

大部分时间都在等待IO(input,output)。例如:Scanner读取用户的输入

(3)分情况讨论

到底在线程池中添加多少线程数量合适呢?

如果线程多为CPU类型的,那线程数目尽量不要超过N

如果线程多为IO类型的,那线程数目就可以远远超过N

但是具体开发肯定是需要我们多次测试,通过观察系统资源消耗,来找出最合适的添加数目的。

四:通过代码实现简单的线程池

1:思路

大逻辑其实就是,把创建的任务提交上去,再把任务取出来,在run执行就可以了就是这么简单

我们用到的IDEA自带的顺序表,阻塞队列BlockingQueue都其实是一个工具罢了~~

2:代码示例

package thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2024-09-27
 * Time: 16:48
 */
class MyThreadPoolExecutor{
    //2:创建一个顺序表来接收创建的线程
    private List<Thread> threadList = new ArrayList<>();
    //4创建一个容量合适的阻塞队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue(1000);
    //1:通过一个循环,n的值,来控制产生的线程的数量
    public MyThreadPoolExecutor(int n){
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(()->{
                //6:把要做的任务从任务队列中不停地取出来,并且执行
                while(true){
                    try {
                        
                        //带有阻塞的take取出元素
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
            //3:
            threadList.add(t);
        }
    }
    //5:提交runnable到队列里面去
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}
public class ThreadDemon35 {
    //大逻辑其实就是,把创建的任务提交上去,再把任务取出来,在run执行就可以了就是这么简单
    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(4);
        for (int i = 0 ; i < 1000 ; i++){
            //变量捕获
            int n = i;
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务:" + n + "  " + "当前线程为:" + Thread.currentThread().getName());
                }
            });
        }
    }
}

image.gif

image.gif 编辑


相关文章
|
7月前
|
存储 安全 Java
【JavaEE】线程安全
【JavaEE】线程安全
|
7月前
|
设计模式 安全 Java
【JavaEE多线程】从单例模式到线程池的深入探索
【JavaEE多线程】从单例模式到线程池的深入探索
64 2
|
7月前
|
存储 设计模式 监控
【JavaEE】多线程案例-线程池
【JavaEE】多线程案例-线程池
|
7月前
|
存储 缓存 安全
【JavaEE初阶】 线程池详解与实现
【JavaEE初阶】 线程池详解与实现
|
7月前
|
Java 程序员 调度
JavaEE 08 线程池简介
JavaEE 08 线程池简介
48 0
|
7月前
|
Java 调度
JavaEE 多线程01
JavaEE 多线程01
28 0
|
7月前
|
存储 安全 Java
【JavaEE】多线程案例-阻塞队列
【JavaEE】多线程案例-阻塞队列
|
缓存 监控 Java
【JavaEE】多线程之线程池
【JavaEE】多线程之线程池
|
存储 Java 调度
JavaEE-什么是多线程?(一)
JavaEE-什么是多线程?(一)
|
设计模式 消息中间件 安全
【JavaEE】线程案例-单例模式 and 阻塞队列
JavaEE & 线程案例 & 单例模式 and 阻塞队列
65 1

热门文章

最新文章