• 【JavaEE初阶--多线程初阶】实现一个线程池


    1. 什么是线程池

    老铁们可以在博主以前写的博客中了解到,所谓的进程比较重,如果频繁的创建和销毁,这样就会对内存资源的开销会很大,于是我们的解决办法:使用进程池或者线程。

    线程,虽然比进程更轻了,但是如果创建销毁的频率进一步增加,仍然会发现内存资源的开销还是很大,那么我们的解决办法是:使用线程池或者协程。

    所谓的线程池:就是把线程提前创建好,放到池子里。后面面程序中如果要使用到线程的话,就直接从池子里取,就不必从系统这边申请了,当线程使用完之后,也不是会给系统,而是放回池子里,以备下次使用。这样创建销毁线程,速度就更快了

    1.1 为什么线程放到池子中,就比从系统这边申请出来更快呢?

    1.1.1 用户态 vs 内核态

    在这里插入图片描述

    创建线程,本身就需要内核的支持

    创建贤臣的本质就是在内核中搞出一个PCB(任务运行控制块)加到链表中。在我们调用Thread.start()是,其实就跟接地也是要进入内核态来进行的。

    而把创建好的线程放到"池子中",由于池子就是用户态实现的,那么这个放到池子/从池子起初,这个过程不需要这几到内核态,就是纯粹的用户态代码就能完成。

    我们一般认为,纯用户态的操作,效率要比经过内核态处理的操作,效率要更高。,那么这又是为什么呢?

    举一个例子:

    在这里插入图片描述

    还有些老铁会说,我们如果创建线程池会不会浪费很多的空间?

    我们创建线程是当然要有额外的空间开销,但是不能谁是"浪费"(用了,但是没有啥效果,就是浪费,用了有效果,就花得值)

    2. 标准库中的线程池

    • 使用Executors.newFixedThreadPool(10) 能创建出固定包含10个线程的线程池
    • 返回值类型为 ExcutorService
    • 通过ExecutorService.submit()就可以注册一个任务到线程池中。

    让我们看看创建线程池的构造方法:

    在这里插入图片描述

    有一个程序,在这个程序中要并发的/多线程的执行一些任务,如果使用线程池的话,那么这里的线程数要设为多少合适?

    其实针对这个问题,在网上有很多的说法其实都是不正确的。

    网上有这么一种典型的说法,假设机器有N核CPU,线程池的线程数目,就射为1 * N,1.2 * N ,1.5 * N ,2 * N…

    但是只要能回答出一个具体的数字,那么都是错的

    正确的做法:要通过性能擦拭的方式,找到合适的值

    例如:写一个服务器程序,服务器里面通过线程池,多线程的处理请求,就可以对这个服务器进行性能测试。

    比如构造一个请求,发送给服务器,要测试性能,这里的请求就需要构造很多。比如美妙发送500/1000个请求这样的场景,需要构造一个合适的值。

    根据这里不同的线程池的线程数,来观察,程序处理任务的速度,程序持有的CPU的占有率。

    当线程数多了,整体的速度就变快了,但是CPU占用率也就高了、

    当线程少了,整体的速度是会变慢的,但是CPU占用率也会下降。因此我们就需要找一个程序速度能接受,并且CPU占用合理这样的平衡点。

    不同类型的程序,因为单个任务里面CPU上计算的时间和阻塞的时间是分布不相同的,因此我们编一个数字往往是不靠谱的

    我们搞多线程目的就是为了让程序跑的速度变快,但是为啥要考虑不让CPU占用率太高了呢?

    对于线上服务器来说,要留一定的冗余。随时因对一些可能的突发情况(例如双十一等,请求就会暴涨),那么如果本身就把CPU块占满了,这时候突然来一波请求的废纸,此时服务器可能直接就挂了。

    在我们的标准库中还提供了一个简化版本的线程池 Executors

    这个简化版的线程池,本质就是针对ThreadPoolExceutor 进行了封装,还提供了一些默认的参数,

    public class demo3 {
        public static void main(String[] args) {
            //创建一个线程池,在线程池中有10个线程
            ExecutorService pool = Executors.newFixedThreadPool(10);
            //Executors 创建线程的几种方式?
            //newFixedThreadPool:创建固定线程数的线程池
            //newCachedThreadPool:创建线程数目动态增长的线程池
            //newSingleThreadExecutor:创建只包含单个线程的线程池
            //newScheduledThreadPoll:设定延迟时间后执行命令,或者定期执行命令,是进阶版的Timer,具有定时器功能的线程池
            //在线程池中的线程中添加任务
            for(int i = 0;i<100;i++){
                pool.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("hello main");
                    }
                });
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3. 实现一个线程池

    • 先能够描述一个任务(直接使用Runnable)
    • 需要组织任务 (直接使用BlockQueue)
    • 能够描述工作线程
    • 还需要组织这些线程(使用List)
    • 还需要实现,往线程池里添加任务
     //自己实现一个线程池
    class ThreadPool{
        //1.描述一个任务,直接使用runnable
        //2.使用阻塞队列,组织若干个任务
        //3.描述一个线程, 工作线程的功能就是从任务队列中取任务并执行.
        //4.把线程添加到线程池中。
        //5.设置submit()方法,向线程池中的线程中添加任务
        public BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
        static class worker extends Thread{
            BlockingQueue<Runnable> queue = null;
    
            public worker(BlockingQueue<Runnable> queue) {
                this.queue = queue;
            }
            //得到队列中的任务并且执行线程中的任务
    
            @Override
            public void run() {
                 //得到线程池中的任务
                try {
                    Runnable runnable = queue.take();
                    runnable.run(); //执行任务
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        //使用一个数据结构组织线程池中的众多线程
        public List<Runnable> list = new ArrayList<>();
        public ThreadPool(int n){
            for(int i = 0;i<n;i++){
                 //得到每个线程
                worker worker = new worker(queue);
                list.add(worker);
            }
        }
        public void submit(Runnable runnable) throws InterruptedException {
            queue.put(runnable);
        }
    }
    public class demo4 {
        public static void main(String[] args) throws InterruptedException {
            ThreadPool pool = new ThreadPool(10);
            for(int i = 0;i<100;i++){
                pool.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("hello main");
                    }
                });
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
  • 相关阅读:
    imedicallis命令的背后
    (其他) 剑指 Offer 46. 把数字翻译成字符串 ——【Leetcode每日一题】
    RL gym 环境(1)—— 安装和基础使用
    深信服技术认证“SCSA-S”划重点:渗透测试工具使用
    XDM,10.1
    什么是合成数据?
    网络编程套接字
    深度学习记录
    PyTorch中的动态学习率
    [PAT练级笔记] 28 Basic Level 1028 人口普查
  • 原文地址:https://blog.csdn.net/qq_54883034/article/details/126032767