聊聊线程那些事

最近几天也确实有点忙,所以断更了几天,今天就忙里偷闲更新下文章;前几天有小伙伴在CSDN上私聊问我一些多线程方面的问题,所以今天我们就来聊聊多线程;

说起多线程想必大家都能道出一二,比如耳熟能详的创建线程方式有继承Thread类、实现Runnable接口、实现Callable接口等等;但是时代是一直进步的,由于

培训班每年批量复制生产大量的java开发人员,如果我们还停留在实现线程的三种方式上面那就有点危险了。

那我们今天聊什么呢,聊创建线程的第四种方式-->线程池;不要陌生,现在项目中基本都会用到,如果你的项目没有用到那只能证明你的项目很是传统,为什么聊线程池,因为面试官喜欢问,如果你不相信可以

看下下面截图展示:

可以看到不管大厂还是小厂现在都喜欢问线程池,所以我们今天就解决一下它;

首先什么是线程池?

顾名思义就是创建线程的池子,和数据库连接池一样,为什么要用它呢?你想一下如果不用线程池的话难道你每次都new一个线程吗?你要真敢new线程来用,那么我只能说你真牛逼,并且会

告诉你别让老板发现了;

线程池的优势如下:

(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。

创建线程池的方式:

一般通过工具类Executors来创建一共四种方式:

newFixedThreadPool()、newSingleThreadPool、newCachedThreadPool、newScheduledThreadPool()

比如说我用newFixedThreadPool来创建,它只有一个构造方法就是要给一个int类型的线程数,很简单是吧

下面我就用这个来模拟两个线程来处理十个任务,代码如下:

是吧,这十个任务都是通过我线程池里面两个线程处理的,其他三个创建线程池方式与此类似,我就不做多解释了;说白了就是这四种方式都不重要所以我不再赘述了;

面试的时候面试官问你项目中用到哪一种线程池创建方式呢,不要乱说,答案是一个都不用,我们实际开发中用另外一个ThreadPoolExecutor来创建,为什么用这个,因为

阿里巴巴开发手册明确了使用这一种,阿里经过大量实践证明的,我们反驳不了;

不要怀疑,上面这个截图就是阿里开发手册里面的,明确指出了上面几种方式的缺点,你看真的是阿里开发手册

所以重点就是ThreadPoolExecutor,四个构造函数,直接看最多的一个,七个参数,很重要,面试官会问的

corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;

maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;

keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;

unit:keepAliveTime的单位

workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;

threadFactory:线程工厂,用于创建线程,一般用默认即可;

handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;

总结下就是:请求来了先执行核心线程数、核心线程数满了就进入阻塞队列进行等待、队列满了就启用最大线程数、最大线程数也满了就执行拒绝策略(注意默认的是AbortPolicy,抛出异常来表示拒绝你,

一共四种拒绝策略,感兴趣的可以百度下,这个面试也会问到),应该如图展示一样:

任何理论必须有实践来支撑,不然就是空话大话不务实,所以我们上代码,核心线程数2、最大线程5、阻塞队列3、这个时候我模拟6个请求,应该两个请求找核心线程、3个请求进入阻塞队列,剩余的一个开启一个最大线程,也就是只会有三个线程会执行任务,是不是这样子呢,看下图:

没毛病吧老铁,就是这样,说到这我又想起了另外一个面试题,就是线程池的提交方法execute与submit的有什么区别?

1、execute(),执行一个任务,没有返回值。
2、submit(),提交一个线程任务,有返回值。
submit(Callable<T> task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。
submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null;

Future.get方法会使取结果的线程进入阻塞状态,知道线程执行完成之后,唤醒取结果的线程,然后返回结果。

说到这就又想说一下future、futureTask这些东西,太多了下次有机会再说吧;

你以为到这就完了吗?没有,比如刚才提到的阻塞队列、拒绝策略、还有线程池的核心线程数由什么决定呢?

阻塞队列就是另一个知识点了,这里也先不赘述了,拒绝策略我也说了有四种,感兴趣的可以自己私底下了解一下,接下来我们聊聊核心线程数怎么决定;

CPU密集型任务
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

IO密集型任务
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

怎么样铁子们,是不是也没有那么难理解,简单吧;

感兴趣的小伙伴可以扫码关注下公众号哦,公众号会分享高质量的技术文章哦



 

 

 

 

 

 

 



 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酒书

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值