多线程案例③

案例四:线程池

池:1)提前把要用的对象准备好 2)用完的对象不要立即释放,先留着以备不时之需

进程可以解决并发编程,为了避免频繁的创建销毁进程,引入了轻量级进程 => 线程。如果创建销毁线程的频率进一步提高 => 线程的创建销毁开销也不能忽视

解决方案:

1.引入轻量级线程 => 纤程/协程

本质:程序员在用户态代码中进行调度,不是靠内核的调度器调度的,节省了很多开销。

协程是用户代码中,基于线程封装出来的,协程底层是怎样封装的,有不同的实现。

        => 可能是N个协程对应一个线程,也可能是N个协程对应M个线程

2.线程池

        把要使用的线程提前创建好,用完了也不直接释放而是以备下次使用,就节省了创建/销毁线程的开销。在这个过程中,并没有真的频繁的创建/销毁,只是从线程池里取线程使用,用完了就还给线程池

从线程池里取线程,比从系统申请更高效。

从线程池里取线程,纯用户态代码(可控的)

通过系统申请创建线程,需要内核来完成(不太可控)

标准库中的线程池:ThreadPoolExecutor

ThreadPoolExecutor  这个类/构造方法,有很多个参数

ThreadPoolExecutor(int corePoolSize, int maximunPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandle handle)

int corePoolSize 核心线程数(一个线程池里,最少有多少个线程)

int maximunPoolSize 最大线程数(一个线程池里,最多有多少个线程)标准库提供的线程池,持有的线程个数,并非是一成不变的,会根据当前的任务量,自适应线程个数(任务非常多,就多增加几个线程,任务少,就减少几个线程)

long keepAliveTime ,TimeUnit unit

保持存活时间                时间单位(s,min,ms,hour)     

空闲时间超过这个时间阈值,就会被销毁掉

 BlockingQueue<Runnable> workQueue 和定时器类似(也可以设置PriorityQueue),线程池里也会持有很多任务,使用Runnable来作为描述任务的主体。

工厂模式,也是一种常见的设计模式。本质上是给Java的语法填坑的(使用普通的方法来创建对象,就是把构造方法封装了一层)如果把工厂方法放到一个其他的类中,这个类 => 工厂类

 工厂模式 => 通过静态方法封装new操作,在方法内部设定不同的属性来完成对象初始化,构造对象的过程就是工厂模式

 ThreadFactory threadFactory 线程工厂 通过工厂类,来创建线程对象(Thread对象)

在这个类中提供了方法(不一定非得是静态的),让方法封装new Thread的操作,同时给Thread设置一些属性,构造了ThreadFactory 线程工程 。

RejectedExecutionHandle handle 拒绝策略(最重要)

在线程池里,有一个阻塞队列,能够容纳的元素是有上限的 ,当任务队列已经满了,还要继续往队列里添加任务

嵌套类

ThreadPoolExecutor.AbortPolicy        继续添加任务,直接抛出异常

ThreadPoolExecutor.CallRunsPolicy        新的任务由添加任务的线程负责执行

ThreadPoolExecutor.DiscardOldestPolicy        丢弃最老的任务

ThreadPoolExecutor.DiscardPolicy        丢弃最新的任务

         由于ThreadPoolExecutor本身用起来比较复杂,所以标准库还提供了另一个版本,把它封装了一下。

标准库中的线程池:Executors 工厂类

通过这个类来创建出不同的线程池对象(在内部把ThraedPoolExecutor创建好了并设置了不同的参数)

Executors.newSingleThreadExecutor()        只包含单个线程的线程池

Executors.newScheduledThreadPool()        定时器类似,也能延时执行任务

Executors.newCachedThreadPool()        线程数目能够动态扩展

Executors.newFixedThreadPool(int nThreads)        线程数目固定

ThreadPoolExecutor也是通过submit添加任务,只是构造方法不同。

Executor => 简单使用

ThreadPoolExecutor => 高度定制化

不同的程序能够设定的线程数量也是不同的,必须要具体问题具体分析

要看一个线程是cpu密集型任务,还是IO密集型任务。

cpu密集型任务:这个线程的大部分时间都是在cpu上运行进行计算

IO密集型任务:这个线程的大部分时间都是在等待IO,不需要去cpu上运行

        如果一个进程中,所有线程都是cpu密集型的,每个线程所有的工作都是在cpu上执行的。此时,线程数目就不应该超过N(cpu逻辑核心数)

        如果一个进程中,所有线程都是IO密集型的,每个线程所有的工作都是在等待IO。此时,cpu的消耗非常少,线程数目就可以很多,远远超过N(cpu逻辑核心数)

上述两个场景是极端情况的,实际是各占一定比例,具体比例不好确定

        综上,由于程序的复杂性,很难直接对于线程池的线程数量进行估算 => 更合理的做法:通过实验/测试的方式找到合适的线程数目。

        常试给线程池,设定不同的线程数目,分别进行性能测试。

衡量每种线程数目下,总的时间开销和系统资源占比的开销,找到两者之间合适的值。

写代码实现一个简单的线程池,直接写一个固定线程数目的线程池(不考虑线程增加/减少)

1)提供构造方法,指定创建多少个线程

2)在构造方法中,把这些线程都创建好

3)有一个阻塞队列,能够持有要执行的任务

4)提供submit方法,可以添加新的任务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值