JavaEE-多线程编程&线程池

目录

引入线程池的原因

介绍标准库中线程池的参数(高频面试题)

实际开发中,核心线程数设置为多少才合适?

线程池的使用

自己实现一个简单的线程池


像线程池/常量池/内存池/进程池等等,这些池的思想都是一样的------提高效率。

引入线程池的原因

并发编程使用多进程,线程比进程更轻量,在频繁创建销毁方面更有优势,随着时代的发展对频繁有了新的定义,现有的要求下,频繁的创建销毁线程使开销越来越明显了,++那么对于这个问题该如何进行优化呢?++

(1)线程池 (2)协程(也可以叫纤程,可以理解为比线程更加轻量的程序)

线程池/协程能提高效率的原因:

常规时,线程的创建销毁是要由用户态+内核态配合完成,当引入线程池/协程后可以只由用户态就可以完成,不需要和内核态配合完成。直接调用api创建销毁线程,这个过程需要内核完成,而内核完成的工作很多时候是不太可控的(可能内核还要先做别的工作,这个事情是不可控的),如果使用线程池,提前把线程创建好放到用户态中的数据结构,用的时候直接在线程池中获取,用完了再放回线程池,这个过程完全是用户态代码,不需要和内核进行交互。

协程本质也是纯用户态代码,规避内核操作,不同的是用一个内核的线程来表示多个协程,纯用户态,进行协程之间的调度。

介绍标准库中线程池的参数(高频面试题)

标准库线程池使用*++ThreadPoolExecutor++*,使用起来比较复杂,构造方法中包含很多的参数

我们一个一个来看:

(1)corePoolSize 核心线程数,在创建线程池时可以自己设定包含多少核心线程

(2)maximumPoolSize 最大线程数,是核心线程+非核心线程的总数,一个线程池刚创建时只包含核心线程数这么多的线程,线程池提供一个submit方法往里面添加任务,每个任务都是一个runnable对象,++如果当前的任务较少,当前核心线程可以处理好这些任务,那么此时线程池中就只有核心线程在工作;如果任务较多,核心线程干不过来了,这个时候线程池就会创建新的线程来支撑更多的工作,这个过程称为------动态扩展++ 。新创建的线程数就是非核心线程,新创建的非核心线程加上核心线程不能超过最大线程数。

过了一段时间后任务没那么多了,部分的非核心线程就会被GC回收掉,这样既保证了任务多时工作的效率,也保证了任务少时系统的开销小。

实际开发中,核心线程数设置为多少才合适?

++取决于电脑配置(cpu核心数)与程序的实际特点++,可以分为两个大类:

1、cpu密集型:代码要完成的逻辑都是要通过cpu干活来完成,比如说如下这段代码:

java 复制代码
 int count = 0;
        while(true){
            count++;
        }

形如这种代码逻辑都是在进行逻辑判断/算术运算/循环判定等等,这样的逻辑运行后可以立马吃满一个cpu,++如果程序是cpu密集型的,线程数就不应该超过cpu核心数++,因为超过了也是浪费。

2、IO密集型:代码大部分时间都在等待IO,等待IO是不消耗cpu的

例如这样的代码:

java 复制代码
Thread t1 = new Thread(()->{
            Scanner scanner = new Scanner(System.in);
            int num = scanner.nextInt();
            System.out.println(num);
        });

线程最消耗时间的部分是等待输入。输入输出/sleep/操作硬盘/操作网络等操作都可能会让线程等待IO。

++如果你的代码是IO密集型,那它的瓶颈不在cpu上,每个cpu只消耗一点点,线程数取决于其他方面(网络程序/网卡带宽),此时可以在硬件限制下多创建些线程。++

小结:以上两种情况都太理性化了,在实际开发中大多是一个程序将cpu密集型与IO密集型都包含了,对于线程数目设置多少最合适这个问题,答案是:根据实际实验找出适合的值,对程序进行性能测试,通过设定不同的线程数,根据实际程序的响应速度和系统开销权衡利弊,最终得到一个你觉得最合适的线程数。

继续分析线程池构造方法的参数列表

(3)keepAliveTime:允许非核心线程空闲时存活的最大时间,数值

(4)Timeunit:上面存活时间的单位

(5)BlockingQueue<Runnable>:++线程池的任务队列++。线程池提供一个submit方法,让线程将任务交给线程池,线程池内部有一个类似队列的数据结构来存储runnable对象,要执行的任务就是runnable对象对应的run方法里的内容。

(6)ThreadFactory:线程工厂。工厂模式也是一种经典的设计模式,主要是为了解决基于构造方法创建对象会产生错误的逻辑。这个线程工厂主要是在批量创建线程时提前设置好了属性,线程工厂在它的方法中提前把线程的属性初始化好了。

重点:(7)RejectedExecutionHandler:拒绝策略

假设:如果当前任务队列满了,仍然要添加任务,那么线程池就会给出四种拒绝策略来告诉线程异常,(a)ThreadPoolExecutor.AbortPolicy:直接抛出异常,让程序员知道此时线程池已经满了

(b)ThreadPoolExecutor.CallerRunsPolicy:哪个线程向线程池中添加任务就由哪个线程自己执行,线程池本身不管了。

(c)ThreadPoolExecutor.DiscardOldestPolicy:丢弃最老的任务,将新的任务放到任务队列中排队。

(d)ThreadPoolExecutor.DiscardPolicy:丢弃最新的任务,按照原有的节奏继续执行,无视新任务。

线程池的使用

标准库的ThreadPoolExecutor使用起来比较费劲,标准库自己提供了几个工厂类,对于上述进程池进一步封装了,如果只是想简单使用一下,那工厂类就够了;如果想更精细的控制就使用原生的ThreadPoolExecutor。

这些是标准库提供的创建线程池的工厂方法:

这四个工厂方法的介绍放在了代码的注释中

java 复制代码
Executors.newCachedThreadPool(); //创建一个普通线程池,会根据任务数量自行扩容
        Executors.newFixedThreadPool(10);//创建一个固定线程数量的线程池
        Executors.newSingleThreadExecutor();//创建一个只包含单线程的线程池
        Executors.newScheduledThreadPool(10);//创建一个固定线程数量,但延迟执行的线程池

栗子:使用第一种方法创建一个线程池并添加任务

java 复制代码
Thread t2 = new Thread(()->{
            ExecutorService service = Executors.newCachedThreadPool();
            for (int i = 0; i < 1000; i++) {
                int count = i;
                service.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(count+" "+Thread.currentThread().getName());
                    }
                });
            }
        });

        t2.start();

截取的一部分运行结果,可以看出自动创建了多个线程来进行该任务。

自己实现一个简单的线程池

一个线程池要有:(1)若干个线程,具体多少个看调用时想设置为多少个 (2)有任务队列,元素为runnable (3)submit方法,将任务添加到任务队列

第一步:实现自己的线程池类,++要有任务队列->BlockingQueue;要有最大线程数与核心线程数,在构造方法中最大线程数就是调用时给定的值,核心线程数需要用循环创建线程,每个线程都要执行任务;submit方法将任务添加到队列中++。初步框架已经列好,代码为:

java 复制代码
class MyThreadPool{
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
    private int maxPoolSize = 0;
    public MyThreadPool(int corePoolSize,int maxPoolSize){
        this.maxPoolSize = maxPoolSize;
        for (int i = 0; i < corePoolSize ; i++) {
            Thread t = new Thread(()->{
                try {
                    Runnable runnable = queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
    }

    void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

第二步:上述代码的问题++(1)每个线程创建完成后需要不停的执行工作,所以try catch内的任务应该使用 while循环进行 (2)submit中应该对任务数设置一个阈值,超过这个值说明任务数量太多了需要创建新的线程来工作,但还需要用总线程数来限制,创建的线程数不得大于最大线程数,想记录任务数量可以使用顺序表这样的数据结构来记录数量。++优化后的代码为:

java 复制代码
class MyThreadPool{
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
    private int maxPoolSize = 0;
    List<Runnable> list = new ArrayList<>();

    public MyThreadPool(int corePoolSize,int maxPoolSize){
        this.maxPoolSize = maxPoolSize;
        for (int i = 0; i < corePoolSize ; i++) {
            Thread t = new Thread(()->{
                try {
                    while (true){
                        Runnable runnable = queue.take();
                        runnable.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
            list.add(t);
        }
    }

    void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
        if(queue.size() >= 100 && list.size() < maxPoolSize){
            Thread t = new Thread(()->{
                while (true){
                    try{
                        Runnable task = queue.take();
                        task.run();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }

                }
            });
            t.start();
            
        }
    }
}

这样,一个简单的类似线程池的代码就创建完了。

道阻且长,行则将至

感谢观看

相关推荐
跃ZHD2 分钟前
前后端分离,Jackson,Long精度丢失
java
blammmp23 分钟前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵41 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong1 小时前
Java反射
java·开发语言·反射
九圣残炎1 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge1 小时前
Netty篇(入门编程)
java·linux·服务器
Re.不晚2 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐2 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。2 小时前
Spring Boot 配置文件
java·spring boot·后端
顾北川_野2 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java