收下膝盖!入职阿里2年的堂姐教我Java多线程-线程的概念和创建

收下膝盖!入职阿里2年的堂姐教我Java多线程-线程的概念和创建


前言

声明:该文章中所有测试都是在JDK1.8的环境下。

该文章是我在学习Java中的多线程这方面知识时,做的一些总结和记录。

如果有不正确的地方请大家多多包涵并作出指点,谢谢!

一、基础概念

我们知道CPU执行代码都是一条一条顺序执行的,所以本质上单核CPU的电脑不会在同一个时间点执行多个任务。但是在现实中,我们在使用电脑的时候,可以一边聊微信,一边听歌。那这是怎么做到的呢?其实操作系统多任务就是让CPU对多个任务轮流交替执行。

举个例子:在一个教室中同时坐着一年级,二年级,三年级三批学生,老师花一分钟教一年级,花一分教二年级,花一分钟教三年级,这样轮流下去,看上去就像同时在教三个年级。

同样的,我们使用电脑时,一边聊微信,一边听歌也是这个原理。CPU让微信执行0.001秒,让音乐播放器执行0.001秒,在我们看来,CPU就是在同时执行多个任务。

1.1 程序、进程和线程的概念

程序:被存储在磁盘或其他的数据存储设备中的可执行文件,也就是一堆静态的代码。

进程:运行在内存中可执行程序实例

线程:线程是进程的一个实体,是CPU调度和分派的基本单位。

看着这些概念是不是很抽象,看得很不舒服,那么下面我来用实例解释一下以上几个概念。

1.2 程序的运行实例

上面说到,我们使用电脑时,可以一边聊微信,一边听歌。那这些软件运行的整个过程是怎样的呢?



我用一张图来总结一下整个过程:



根据上面内容对于线程概念的了解,是否有个疑问,线程是怎么创建出来的?带着这个疑问我们就来学习一下java中的线程是怎么如何创建的。

二、线程的创建

2.1 Thread类的概念

java.lang.Thread类代表线程,任何线程都是Thread类(子类)的实例。

2.2 常用的方法





2.3 创建方式

2.3.1 自定义Thread类创建

自定义类继承Thread类并根据自己的需求重写run方法,然后在主类中创建该类的对象调用start方法,这样就启动了一个线程。

示例代码如下:

到这里大家会不会有以下一个疑问,看示例代码:

我们不调用start方法,而是直接调用run方法,发现结果和调用start方法一样,他们两个方法的区别是啥呢?

我们在主方法中也加入一个打印1-20的数,然后分别用run方法和start方法进行测试,实例代码如下:

从上面的例子可知:

2.3.2 通过实现Runnable接口实现创建

自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法。

示例代码如下:

到这里大家会不会有一个疑问呢?

我在SunRunnableRunTest类的main方法中也实例化了Thread类,为什么该线程调用的是实现了Runnable接口的SubRunnableRun类中的run方法,而不是Thread类中的run方法。

为了解决该疑问,我们就进入Thread类去看一下源码,源码调用过程如下:

  1. 从上面的SunRunnableRunTest类中代码可知,我们创建线程调用的是Thread的有参构造方法,参数是Runnable类型的。



  1. 进入到Thread类中找到该有参构造方法,看到该构造方法调用init方法,并且把target参数继续当参数传递过去。
  2. 转到对应的init方法后,发现该init方法继续调用另一个重载的init方法,并且把target参数继续当参数传递过去。




  1. 继续进入到重载的init方法中,我们发现,该方法中把参数中target赋值给成员变量target。



  1. 然后找到Thread类中的run方法,发现只要Thread的成员变量target存在,就调用target中的run方法。



通过查看源码,我们可以知道为什么我们创建的Thread类调用的是Runnable类中的run方法。

2.3.3 匿名内部类的方式实现创建

上面两种创建线程的方式都需要单独创建一个类来继承Thread类或者实现Runnable接口,并重写run方法。而匿名内部类可以不创建单独的类而实现自定义线程的创建。

示例代码如下:

这两个利用匿名内部类创建线程的方式还能继续简化代码,尤其是使用Runnable接口创建线程的方式,可以使用Lambda表达式进行简化。

示例代码如下:

2.3.4 通过实现Callable接口创建

通过上面几个例子,我们了解了两种创建线程的方式,但是这两种方式创建线程存在一个问题,就是run方法是没有返回值的,所以如果我们希望在线程结束之后给出一个结果,那就需要用到实现Callable接口创建线程。



(1)Callable接口

从Java5开始新增创建线程的第三种方式为实现java.util.concurrent.Callable接口。

常用方法如下:



我们知道启动线程只有创建一个Thread类并调用start方法,如果想让线程启动时调用到Callable接口中的call方法,就得用到FutureTask类。

(2)FutureTask类

java.util.concurrent.FutureTask类实现了RunnableFuture接口,RunnableFuture接口是Runnable和Future的综合体,作为一个Future,FutureTask可以执行异步计算,可以查看异步程序是否执行完毕,并且可以开始和取消程序,并取得程序最终的执行结果,也可以用于获取调用方法后的返回结果。

常用方法如下:



从上面的概念可以了解到FutureTask类的一个构造方法是以Callable为参数的,然后FutureTask类是Runnable的子类,所以FutureTask类可以作为Thread类的参数。这样的话我们就可以创建一个线程并调用Callable接口中的call方法。

实例代码如下:

2.3.5 线程池的创建

线程池的由来:

在讲线程池之前,先来讲一个故事,一个老板开个饭店,但这个饭店很奇怪,每来一个顾客,老板就去招一个新的大厨来做菜,等这个顾客走后,老板直接把这个大厨辞了。如果是按这种经营方式的话,老板每天就忙着招大厨,啥事都干不了。

对于上面讲的这个故事,我们现实生活中的饭店老板可没有这么蠢,他们都是在开店前就直接招了好几个大厨候在厨房,等有顾客来了,直接做菜上菜,顾客走后,厨师留在后厨待命,这样就把老板解放了。

现在我们来讲一下线程池的由来:比如说服务器编程中,如果为每一个客户都分配一个新的工作线程,并且当工作线程与客户通信结束时,这个线程被销毁,这就需要频繁的创建和销毁工作线程。如果访问服务器的客户端过多,那么会严重影响服务器的性能。

那么我们该如何解放服务器呢?对了,就像上面讲的饭店老板一样,打造一个后厨,让厨师候着。相对于服务器来说,就创建一个线程池,让线程候着,等待客户端的连接,等客户端结束通信后,服务器不关闭该线程,而是返回到线程中待命。这样就解放了服务器。

线程池的概念:

首先创建一些线程,他们的集合称为线程池,当服务器接收到一个客户请求后,就从线程池中取出一个空余的线程为之服务,服务完后不关闭线程,而是将线程放回到线程池中。

相关类和方法:

使用newFixedThreadPool方法创建线程池

从结果上可以看出,这四个任务分别被线程池中的固定的两个线程所执行,线程池也不会创建新的线程来执行任务。

使用newCachedThreadPool方法创建线程池

从结果上可以看出,线程池根据任务的数量来创建对应的线程数量。

使用newSingleThreadExecutor的方法创建线程池

从结果可以看出,该方法创建的线程可以保证任务执行的顺序。

使用newScheduledThreadPool的方法创建线程池

从结果可以看出,该方法创建的线程池可以分配已有的线程执行一些需要延迟的任务。

使用newSingleThreadScheduledExecutor方法创建线程池

从结果可以看出,该方法创建的线程池只有一个线程,该线程去执行一些需要延迟的任务。

使用newWorkStealingPool方法创建线程池

从结果可以看出,该方法会创建一个含有足够多线程的线程池,来维持相应的并行级别,任务会被抢占式执行。(任务执行顺序不确定)

使用ThreadPoolExecutor创建线程池

在编写示例代码之前我先来讲一个生活的例子(去银行办理业务):

描述业务场景:银行一共有4个窗口,今天只开放两个,然后等候区一共3个位置。如下图所示:



如果银行同时办理业务的人小于等于5个人,那么正好,2个人先办理,其他的人在等候区等待。如下图所示:



如果银行同时办理业务的人等于6个人时,银行会开放三号窗口来办理业务。如下图所示:



如果银行同时办理业务的人等于7个人时,银行会开放四号窗口来办理业务。如下图所示:



如果银行同时办理业务的人大于7个人时,则银行大厅经理就会告诉后面的人,该网点业务已满,请去其他网点办理。



现在我们再来看一下我们的ThreadPoolExecutor构造方法,该构造方法最多可以设置7个参数:



参数介绍:

1.corePoolSize:核心线程数,在线程池中一直存在的线程(对应银行办理业务模型:一开始就开放的窗口) 2.maximumPoolSize:最大线程数,线程池中能创建最多的线程数,除了核心线程数以外的几个线程会在线程池的任务队列满了之后创建(对应银行办理业务模型:所有窗口) 3.keepAliveTime:最大线程数的存活时间,当长时间没有任务时,线程池会销毁一部分线程,保留核心线程 4.unit:时间单位,是第三个参数的单位,这两个参数组合成最大线程数的存活时间

从结果中可以看出,只有两个核心线程在执行任务。

当任务数大于核心线程数+等待队列数量的总和,但是小于等于最大线程数时:

从结果中可以看出,启动了最大线程来执行任务。

当任务数大于最大线程数时:

从结果中可以看出,任务大于最大线程数,使用拒绝策略直接抛出异常。

三、总结

本文介绍了三种线程的创建方式:

介绍了七种线程池的创建方式:

最新2021整理收集的一些高频面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud、jvm、源码、算法等详细讲解,也有详细的学习规划图,面试题整理等,需要获取这些内容的朋友私信免费获取:私信回复暗号:java

最后,祝大家早日学有所成,拿到满意 offer,快速 升职加薪,走上人生巅峰。记得给我一个一键三连哦!老子爱你们!

代做工资流水公司舟山开流水单绍兴开消费贷流水莆田查询企业贷流水天津工资流水app截图开具泰安背调流水开具揭阳查贷款工资流水无锡公司银行流水报价包头入职工资流水打印宜春银行对公流水开具遵义签证工资流水制作洛阳查在职证明台州贷款流水报价石家庄查询日常消费流水许昌银行流水PS多少钱银行流水账单查询淄博薪资流水查询重庆薪资流水公司舟山代开转账银行流水贵阳做工资流水app截图大庆背调工资流水样本威海自存银行流水价格佛山制作自存银行流水菏泽房贷银行流水 模板合肥企业银行流水打印杭州制作工资流水账单新乡做个人工资流水合肥打入职流水北京车贷银行流水 多少钱湖州公司银行流水公司贵阳对公银行流水样本香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声卫健委通报少年有偿捐血浆16次猝死汪小菲曝离婚始末何赛飞追着代拍打雅江山火三名扑火人员牺牲系谣言男子被猫抓伤后确诊“猫抓病”周杰伦一审败诉网易中国拥有亿元资产的家庭达13.3万户315晚会后胖东来又人满为患了高校汽车撞人致3死16伤 司机系学生张家界的山上“长”满了韩国人?张立群任西安交通大学校长手机成瘾是影响睡眠质量重要因素网友洛杉矶偶遇贾玲“重生之我在北大当嫡校长”单亲妈妈陷入热恋 14岁儿子报警倪萍分享减重40斤方法杨倩无缘巴黎奥运考生莫言也上北大硕士复试名单了许家印被限制高消费奥巴马现身唐宁街 黑色着装引猜测专访95后高颜值猪保姆男孩8年未见母亲被告知被遗忘七年后宇文玥被薅头发捞上岸郑州一火锅店爆改成麻辣烫店西双版纳热带植物园回应蜉蝣大爆发沉迷短剧的人就像掉进了杀猪盘当地回应沈阳致3死车祸车主疑毒驾开除党籍5年后 原水城县长再被查凯特王妃现身!外出购物视频曝光初中生遭15人围殴自卫刺伤3人判无罪事业单位女子向同事水杯投不明物质男子被流浪猫绊倒 投喂者赔24万外国人感慨凌晨的中国很安全路边卖淀粉肠阿姨主动出示声明书胖东来员工每周单休无小长假王树国卸任西安交大校长 师生送别小米汽车超级工厂正式揭幕黑马情侣提车了妈妈回应孩子在校撞护栏坠楼校方回应护栏损坏小学生课间坠楼房客欠租失踪 房东直发愁专家建议不必谈骨泥色变老人退休金被冒领16年 金额超20万西藏招商引资投资者子女可当地高考特朗普无法缴纳4.54亿美元罚金浙江一高校内汽车冲撞行人 多人受伤

代做工资流水公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化