一次洗脚引发的线程池思考

一次洗脚引发的线程池思考,文末有惊喜

上周末和好朋友大凯子约好周末去洗脚放松一下,毕竟忙了一周了,又加了几天班,身心疲惫,好的身体是革命的本钱,男人可得好好爱惜自己的身体,不能倒在革命的道路上,于是第二天大凯子早早的就来找我,说他找到了一家技师很不错的足浴馆,里面的技师身材很棒,颜值很高,肤白貌美大长腿,重要的是按摩的技术在大众点评上获得客户的一致好评,我被他说的深深的诱惑住了,说走就走,坐着5站地铁,不行6百米就来到了他说的这家足浴馆,由于这家店口碑特别好,也不支持线上预约,里面也是人满为患,得排队,我问大凯子要不要明天再来,因为这家要等很久,我们排到了15号和16号,按平均半小时一位的话,这家店5个技师,我俩也要等一个半小时,突然发现隔壁还有一家,透过玻璃窗看,似乎里面人不多,应该不用排队,大凯子说这家店的技师漂亮,我们等等吧,抽根烟,打两把王者时间就过去了,为了支持大凯子的想法,我同意了, 就为了看看大凯子口中说的技师到底多好看,捏脚的技术多好。
这时候大凯子不经意间说了一句:要是两位女老板可以帮我捏脚就好了,就可以少排队一段时间了。
然后我说:这不就是线程池吗?
大凯子一脸疑问:啥?线程池?我们洗个脚你也能想到线程池?来来来,给我讲讲,我只是会用,里面的原理不太懂。

故事开始了,我们结合这几个点来展开
1.第一个什么是线程池?
2线程池都做了什么?
3线程池都有哪写参数?
4.线程池工作原理是什么?
5.为什么阿里巴巴官方手册不推荐使用自带的线程池工具类?

我:线程池就是足浴馆
大凯子疑问的说到:你说啥?
我:你先别急,听我慢慢说,线程池之所以叫线程池,因为他就是一个大池子,负责协调调度里面的线程
线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,合理的使用线程池对线程进行统一分配、调优和监控,有以下好处:
1、降低资源消耗;
2、提高响应速度;
3、提高线程的可管理性。

Java1.5中引入的Executor框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。
在这里插入图片描述

1、Executors.newFixedThreadPool(10)初始化一个包含10个线程的线程池executor;
2、通过executor.execute方法提交20个任务,每个任务打印当前的线程名;
3、负责执行任务的线程的生命周期都由Executor框架进行管理;

ThreadPoolExecutor
Executors是java线程池的工厂类,通过它可以快速初始化一个符合业务需求的线程池,如Executors.newFixedThreadPool方法可以生成一个拥有固定线程数的线程池。
在这里插入图片描述

其本质是通过不同的参数初始化一个ThreadPoolExecutor对象,具体参数描述如下:

corePoolSize(相当于店里的5名技师,店里的主力
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

maximumPoolSize(相当于店里的5名技师+2名女老板(排队人员超过30个,也就是队列满了,老板娘才会上噢),老板之前也是技师出身,特别忙的时候也会为客人服务
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;

keepAliveTime(2名女老板忙的时候也在后台给客人服务,超过一定时间(比如15分钟)没客人来,两位女老板就换上衣服去前台了
线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用;

unit
keepAliveTime的单位;

workQueue(5名女技师每个人都在服务客人,其他客人就得排队
用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:
1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;(如果调用无参构造方法则创建的容量是Integer.MAX_VALUE为无界,也可以自己在构造方法里传参数指定队列容量,变成有界,总有面试官故意问这种坑比问题,LinkedBlockingQuene是有界还是无界的
3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;

1:是无界的,队列的size始终为0,每个put操作需要等待take操作,反之也是一样。
2:默认是非公平。
3:在线程池中使用SynchronousQueue队列时,线程池内部只调用 offer添加,所以在未消费元素时,始终添加不进去,只有消费了元素,创建了不指向元素的头结点,之后offer才能添加元素。

4、priorityBlockingQuene:具有优先级的无界阻塞队列;(VIP 客人可以插队,就想银行大客户vip有专门的通道一样

threadFactory
创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。
在这里插入图片描述

handler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
1、AbortPolicy:直接抛出异常,默认策略;
2、CallerRunsPolicy:用调用者所在的线程来执行任务;
3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4、DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

线程池执行流程

这里用一个图来说明线程池的执行流程
在这里插入图片描述
结合源码来看下
1、先看一下线程池的executor方法

在这里插入图片描述
①:判断当前活跃线程数是否小于corePoolSize,如果小于,则调用addWorker创建线程执行任务

②:如果不小于corePoolSize,则将任务添加到workQueue队列。

③:如果放入workQueue失败,则创建线程执行任务,如果这时创建线程失败(当前线程数不小于maximumPoolSize时),就会调用reject(内部调用handler)拒绝接受任务。
2、再看下addWorker的方法实现
在这里插入图片描述
这块代码是在创建非核心线程时,即core等于false。判断当前线程数是否大于等于maximumPoolSize,如果大于等于则返回false,即上边说到的③中创建线程失败的情况。

addWorker方法的下半部分:

在这里插入图片描述
①创建Worker对象,同时也会实例化一个Thread对象。

②启动启动这个线程

3、再到Worker里看看其实现

在这里插入图片描述

可以看到在创建Worker时会调用threadFactory来创建一个线程。上边的②中启动一个线程就会触发Worker的run方法被线程调用。

4、接下来咱们看看runWorker方法的逻辑
在这里插入图片描述
线程调用runWoker,会while循环调用getTask方法从workerQueue里读取任务,然后执行任务。只要getTask方法不返回null,此线程就不会退出。
5、最后在看看getTask方法实现
在这里插入图片描述
①咱们先不管allowCoreThreadTimeOut,这个变量默认值是false。wc>corePoolSize则是判断当前线程数是否大于corePoolSize。

②如果当前线程数大于corePoolSize,则会调用workQueue的poll方法获取任务,超时时间是keepAliveTime。如果超过keepAliveTime时长,poll返回了null,上边提到的while循序就会退出,线程也就执行完了。

如果当前线程数小于corePoolSize,则会调用workQueue的take方法阻塞在当前。

线程池一共可以创建的4种线程池及工作流程

newFixedThreadPool
在这里插入图片描述

初始化一个指定线程数的线程池,其中corePoolSize == maximumPoolSize,使用LinkedBlockingQuene作为阻塞队列,不过当线程池没有可执行任务时,也不会释放线程。

newCachedThreadPool
在这里插入图片描述
1、初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;
2、和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;

所以,使用该线程池时,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。

newSingleThreadExecutor
在这里插入图片描述
初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行,内部使用LinkedBlockingQueue作为阻塞队列。

newScheduledThreadPool
在这里插入图片描述
初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。

线程池内部状态一共5种

在这里插入图片描述
其中AtomicInteger变量ctl的功能非常强大:利用低29位表示线程池中线程数,通过高3位表示线程池的运行状态:
1、RUNNING:-1 << COUNT_BITS,即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;
2、SHUTDOWN: 0 << COUNT_BITS,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
3、STOP : 1 << COUNT_BITS,即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
4、TIDYING : 2 << COUNT_BITS,即高3位为010;
5、TERMINATED: 3 << COUNT_BITS,即高3位为011;

1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。

根据阻塞产生的原因不同,阻塞状态又可以分为三种:

a.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

b.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

c.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
在这里插入图片描述
图片来自网络
状态流转图如下:
在这里插入图片描述

任务提交

线程池框架提供了两种方式提交任务,根据不同的业务需求选择不同的方式。

Executor.execute()在这里插入图片描述
通过Executor.execute()方法提交的任务,必须实现Runnable接口,该方式提交的任务不能获取返回值,因此无法判断任务是否执行成功。

ExecutorService.submit()
在这里插入图片描述
通过ExecutorService.submit()方法提交的任务,可以获取任务执行完的返回值,因为是基于Callable实现的,用Future来接收执行结果。
5.为什么阿里巴巴官方手册不推荐使用自带的线程池工具类?
在这里插入图片描述从上图中也可以看出,Executors的创建线程池的方法,创建出来的线程池都实现了ExecutorService接口。
在这里插入图片描述newCachedThreadPool和newScheduledThreadPool这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,就有可能导致内存溢出。

避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。

在这里插入图片描述

讲到这里,大凯子终于懂了,这时候也排我俩了,一位服务员把我俩带到了后台,别说环境还真不错,妹纸也很漂亮,按摩的手法相当可以,198花的不亏/

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是洗脚的前端页面的详细代码: ``` <template> <div class="foot-washing"> <el-form :model="form" ref="form" label-width="80px" class="demo-ruleForm"> <el-form-item label="姓名" prop="name" :rules="[ {required: true, message: '请输入姓名', trigger: 'blur'}]"> <el-input v-model="form.name" placeholder="请输入姓名"></el-input> </el-form-item> <el-form-item label="电话" prop="phone" :rules="[ {required: true, message: '请输入电话', trigger: 'blur'}, {pattern: /^1\d{10}$/, message: '手机号格式不正确', trigger: 'blur'}]"> <el-input v-model="form.phone" placeholder="请输入电话"></el-input> </el-form-item> <el-form-item label="性别" prop="gender" :rules="[ {required: true, message: '请选择性别', trigger: 'blur'}]"> <el-radio-group v-model="form.gender"> <el-radio label="male">男</el-radio> <el-radio label="female">女</el-radio> </el-radio-group> </el-form-item> <el-form-item label="服务类型" prop="serviceType" :rules="[ {required: true, message: '请选择服务类型', trigger: 'blur'}]"> <el-select v-model="form.serviceType" placeholder="请选择服务类型"> <el-option label="足疗" value="foot-massage"></el-option> <el-option label="泡脚" value="foot-soak"></el-option> <el-option label="换脚面" value="foot-skin-change"></el-option> </el-select> </el-form-item> <el-form-item label="服务日期" prop="serviceDate" :rules="[ {required: true, message: '请选择服务日期', trigger: 'blur'}]"> <el-date-picker v-model="form.serviceDate" type="date" placeholder="请选择服务日期"></el-date-picker> </el-form-item> </el-form> <div class="button-group"> <el-button type="primary" @click="submitForm('form')">提交</el-button> <el-button>取消</el-button> </div> </div> </template> <script> export default { name: 'FootWashing', data() { return { form: { name: '', phone: '', gender: '', serviceType: '', serviceDate: '' } } }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { alert('提交成功') } else { alert('提交失败,请输入正确信息') return false; } }) } } } </script> <style scoped> .foot-washing { margin: 20px auto; width: 60%; } .button-group { margin-top: 20px; text-align: center; } </style> ``` 这是一个基于 Vue 和 ElementUI 框架的简单洗脚预约前端页面,包含姓名、电话、性别、服务类型、服务日期等信息的输入和提交功能。 注意:以上代码仅供参考,具体实现可能需要根据业务需求进行调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值