JUC5(线程池)

线程池

你要吃饭,于是你买了个碗。吃完饭后,你懒得洗碗,于是就把碗砸了。但是下一次吃饭时,你又没有碗了。于是你发现了问题:1.每次都要买碗,浪费时间;2.每次吃完都要把碗摔了,浪费资源。

解决方案:准备一个碗柜即可。

在没有接触线程池之前,我们写多线程都是用到线程的时候就创建,用完之后线程就消失,这会浪费操作系统的资源。所以,我们也准备一个容器------线程池,用来存放线程。

起初线程池为空,当我们提交一个任务时,线程池会自动创建线程来处理这个任务,处理完了再把线程放到线程池中。这样当我们提交第二个任务时,就不需要再创建新的线程了,用线程池中的线程即可。

特殊情况:如果当我们提交第二个任务时,第一个线程还在执行任务,此时线程池就会创建新的线程去执行这第二个任务。如果我们又提交了很多个任务,线程池也同样会创建线程执行这些任务。不过,这并不意味着线程池创建线程的数量没有上限,而且我们可以自己手动设置线程数量上限。如果我们设置最大线程数量为3,那么倘若提交了5个任务,后面的2个任务只能先排队等着。

线程池代码实现

Executors:线程池的工具类,通过调用方法返回不同类型的线程池对象。

方法名称 说明
public static ExecutorService new CachedThreadPool() 创建一个没有上限的线程池
public static ExecutorService new newFixedThreadPool(int nThreads) 创建有上限的线程池

注意:没有上限不是真的可以无穷大,上限为int的最大值

java 复制代码
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}
java 复制代码
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        //1.获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();
​
        //2.提交任务
        pool1.submit(new MyRunnable());
        
        //3.销毁线程池
        pool1.shutdown();
    }
}
java 复制代码
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        //1.获取线程池对象
        ExecutorService pool1 = Executors.newFixedThreadPool(3);
​
        //2.提交任务
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
​
        //3.销毁线程池
        //pool1.shutdown();
    }
}

可以通过debug验证确实创建了3个线程处理任务。关注两个参数:workQueue和pool(pool里面有pool size\active threads\queued tasks\completed tasks这些参数)

自定义线程池

核心元素:核心线程数量(非负)、线程池中最大线程数量(大于等于核心线程数量)、空闲时间(值)(非负)、空闲时间(单位)(用TimeUnit指定)、阻塞队列(非null)、创建线程的方式(非null)、执行任务过多时的解决方案(非null)。

任务拒绝策略 说明
ThreadPoolExecutor.AbortPolicy 默认策略:丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常,不推荐
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 调用任务的run()方法,绕过线程池直接执行
java 复制代码
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                6,//最大线程数量
                60,//最大空闲时间的值
                TimeUnit.SECONDS,//最大空闲时间的单位
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }
}
最大并行数

以4核8线程的CPU为例, 相当于CPU有4个大脑,只不过Intel的超线程技术让它把原来的4个大脑虚拟成8个,所以最大并行数为8.在设备管理器的处理器中就可以看到。或者打开任务管理器的性能,看右下角的参数即可。但是有些操作系统不会把所有的CPU资源都交给一个软件用,所以这个方法不能说是百分百稳妥。也可以在IDEA中使用代码查看:

复制代码
System.out.println(Runtime.getRuntime().availableProcessors());

该方法会返回JVM可用处理器的数目。如果和任务管理器中看到的参数一样,那么说明对于你的操作系统来说,Java可以使用所有的资源。

线程池的合适大小

CPU密集型运算:计算较多,读取本地文件或读取数据库的操作较少。对于这种情况,线程池的合适大小为最大并行数+1,可以实现CPU的最优利用率。

对于I/O密集型运算:读取本地文件或读取数据库的操作较多,对于这种情况,线程池的合适大小为:

最大并行数*期望CPU利用率*总时间/CPU计算时间

其中总时间为CPU计算时间+等待时间。

例:从本地文件中读取两个数据,并进行相加。这个过程可以拆分成两步:1.读取两个数据;2.相加

我们假设这两个步骤都用时1秒,步骤一与CPU无关,步骤二与CPU相关,因此总时间为2秒,CPU计算时间为1秒。

至于怎么获取CPU的计算时间与等待时间,我们需要使用工具进行测试,例如thread dump

相关推荐
松仔log42 分钟前
JetPack——Paging3+Room
android·java·zoom
Lei活在当下6 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.6 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
tongluowan0077 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
身如柳絮随风扬8 小时前
Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
java·firefox·jar
云烟成雨TD8 小时前
Spring AI Alibaba 1.x 系列【62】时光旅行(Time-Travel)
java·人工智能·spring
浩少7029 小时前
【无标题】
java·开发语言
一棵白菜9 小时前
java 学习
java
卷毛的技术笔记9 小时前
Java后端硬核实战:用Spring AI Alibaba+Redis给LLM装上“超强记忆中枢”
java·人工智能·redis·后端·spring·ai·系统架构
AKA__Zas10 小时前
初识多线程(3.0)
java·开发语言·学习方法