线程池独家揭秘不想了解都难

两大线程模型

用户模型

通过应用接口来调用内核空间的cpu

内核模型

操作系统底层自身的线程,对于用户模型的运行无感知,但是提供接口对自己的调用,jvm线程是内核级线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PYqppSRf-1586668307070)(137B582AFC6C4F01B2EEA4062538CBB9)]

为什么使用线程池

大量线程下,会容易导致用户线程对内核线程的控制消耗很大,来回的上下文切换浪费大量时间和内存
,为了避免资源过度浪费,我们需要想出一个可以重用线程执行的任务,比如缓存这种,避免了频繁创造和销毁线程,这就是线程池的池化技术
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fIALHzF8-1586668307070)(242C45E412C540C7A7A857D3578C6545)]

线程池的优势

首先是,可以重用线程,避免频繁创造和销毁,减少内存消耗

其次是,不需要等待随拿随用,速度块

最后是,可以利用线程池对线程进行统一的管理,还有分配调优监控

最常用的线程工具类

Executors

三种常见的线程池

newFixedThreadPool(5) newSingleThreadExecutor() newCachedThreadPool() newWordStealingPool()
这里面是固定了线程数量 单个线程 遇弱则弱,遇强则强 任务窃取,根据核数确定个数
可以缓存一些线程提高速度 固定了线程执行顺序 灵活性很高 适合耗时大量任务的场景

一般来说是自己配,因为线程池创建的参数最大线程数可以为Integer.MAX_VALUE,容易发生危险

我们应当使用ExecutorService自己创建自定义线程池

        int runtime=Runtime.getRuntime().availableProcessors();
        ExecutorService threadpool=new ThreadPoolExecutor(
                2,
                runtime,
                3,
                TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()   //银行满了,还有人进来,不处理这个人的,抛出异常
                );

        try {
            for (int i = 0; i < 9; i++) {
                threadpool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"启动了");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadpool.shutdown();
        }

自定义线程池七大参数

  • int corePoolSize
    银行的空闲办理业务的窗口
  • maxmumPoolSize
    业务最忙的时候的窗口
  • keepAliveTime
    业务忙多开的线程能保存的时间
  • TimeUnit
    保存时间单位
  • BlockingQueue
    阻塞队列,候客区,平时空闲先去候客区,满了再开启忙的时候的窗口
  • ThreadFactory
    创建线程的工厂一般使用默认
  • RejectExecutionHandler
    拒绝策略
    1. 当所有窗口都满就会触发策略,有点像大堂经理
    2. AbortPolicy 丢出异常
    3. DiscarPolicy 丢弃任务
    4. DiscardOldestPolicy 尝试和第一去竞争,再重新执行
    5. CallerRunPolicy 哪里来哪里执行
    6. 自定义的话实现这个RejectExecutionHandler接口,然后重写rejectExecution这个方法即可

最多接收线程数=maxmumPoolSize+BlockingQueue

线程池执行流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EnJIXFOo-1586668307071)(275A3281CD4C429685619D62068324D3)]

线程池状态

线程池生命状态
Running 能接收新任务和处理任务
Shutdown 不接收新任务,但可以处理有的任务
stop 什么都不干,并且打断你处理任务
Tidying 所有线程终止,ctl=0(这是记录状态和任务数量的标记)
Terminated 线程彻底终止,即线程池为Terminated状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9h6rtvfF-1586668307074)(91145A67A1EE470B94421C9CF178BDEF)]

如何设置线程池合理的大小

cpu密集型任务

看看机器能支持多少并发
比较消耗cpu资源,大多是计算任务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aD6Fxcsv-1586668307075)(3C6ED8C4DAA4499CB593BDD95B0CD7AD)]
CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。
可以设置为核数+1

io密集型

频繁i/o交互,不占用cpu资源
不要让CPU闲下来,应加大线程数量,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。
一般是2*核数+1

如果是定时线程池任务

等待的时间cpu处于空闲,更应该加大线程数量,而这个等待时间不确定,所以开放的数量得根据时间得变化而确定
一般是

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。
可以得出一个结论:
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。

原码分析

构造方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mm6poNwE-1586668307077)(536B40F7B5524C63AE747F928F7A1D62)]

execute核心方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xCDQp2ZY-1586668307077)(7786CC4E280D4FBABE465D0797F57D2E)]
在这里插入图片描述

addworker方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-byrOsVdy-1586668307079)(9E2A769290C54CDBAEAD843909E5E00A)]
将当前线程加入work类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PJ8zWTqr-1586668307080)(33456023F204478183F98758178F9C89)]

Work类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-414dFQVR-1586668307080)(BBFC30512BC3460FA8332DADBCFBF88F)]

runWork方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hei2oh0Q-1586668307081)(9674D03DBF2B46838368C1FEA078F67D)]

可以看出是怎么拿到线程然后执行线程的run方法的!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MpFZLMh2-1586668307082)(7B004965B3634D2C869D150BFF0CDBA9)]

发布了105 篇原创文章 · 获赞 19 · 访问量 4967

猜你喜欢

转载自blog.csdn.net/jiohfgj/article/details/105468104