目录
🐳今日良言:得之坦然,失之淡然,争取必然,顺其自然。
🐯一、线程的基本操作
🐭1.线程的创建
🐭2.线程的中断
🐭3.线程的等待
🐭4.获取线程实例
🐭5.线程的休眠
🐕二、线程的状态
🐍1.介绍
🐍2.状态之间的切换条件
🐳今日良言:得之坦然,失之淡然,争取必然,顺其自然。
🐯一、线程的基本操作
🐭1.线程的创建
java中创建线程最核心的类:Thread
兄弟们不仅需要认识并熟练掌握这个类,还需要会读哦,来跟博主读 (si ruan de)
1).继承Thread类,重写run方法
在初学编程时,我们敲的应该都是hello world吧,那么先通过最基本的hello world程序对多线程有一个基本的了解
当运行这段代码后,输出hello world
分析:
1.t.start() 是线程中的特殊方法,作用是启动(创建)一个线程
2.MyThread 这个类继承Thread 类 并重写了父类的run方法,这个run方法主要是描述了要执行的任务是什么.
3.run方法和start的区别
start是创建了一个新的线程,操作系统内核创建新的PCB,并且把要执行的指令交给这个PCB,当PCB被调度到CPU上执行的时候,就执行到了线程run方法中的代码了.
总结:start创建新线程,新线程执行run方法
4.当run方法执行完毕后,新的线程销毁.
上述代码并不能看到并发编程的效果,但是通过下面的代码可以达到并发编程的效果
执行上述代码,打印出如下结果
此时可以看出,两个线程(主线程main和新线程)都在执行各自的任务,并没有说一个执行而另外一个不执行,为了让打印速度慢些,可以加入sleep(休眠,后面会详细介绍)
观察执行结果,会发现,打印main和thread是随机出现的,这是为什么呢?
这是因为:操作系统在调度线程的时候,是'抢占式执行',具体哪个线程先上,哪个线程后上是不确定的,这取决于操作系统调度器的具体实现策略.
可能会有老铁疑惑,不是说线程是有优先级的吗?可以通过设置优先级来控制输出顺序吗?
很遗憾,这是不可以的,虽然有优先级,但是在应用程序层面上无法修改,从应用程序(代码)的角度看到的效果好像是线程之间的调度顺序是'随机'的一样,但是操作系统内核里本身并非是随机的,但是干预因素太多,并且应用程序这一层也无法感应到细节,就只能认为是'随机'的.
可以使用JDK自带的工具jconsole查看当前的java程序中的所有线程.
注:运行上面的代码,在运行的时候查看
默认路径如下:
此时点击打开
注:如果有老铁打开jconsole没有运行的程序,此时可以试试右键,管理员方式运行
然后选中这个正在运行的程序点击连接
点击不安全的连接
然后选中线程这里,下面的这些就是当前java进程中的所有线程
2).实现Runnable接口
3).使用匿名内部类,继承Thread类
4).使用匿名内部类,实现Runnable
5).lambda表达式(最简单)
把任务用lambda表达式来描述,直接把lambda传给Thread构造方法
最后再介绍一下Thread的构造方法和一些属性
构造方法
前两个已经介绍过了,这里说一下后两个,后面两个构造方法就是给创建的线程起一个名字.如下代码
使用jconsole观察
起别名主要是为了调试的时候便于观察,如果不起别名的话,默认是从Thread-0开始往上加.
常见属性
1).getId()
获取到线程的身份标识.
2).getName()
获取到线程的名字
3).getState()
获取线程的状态,这个在后面线程的状态会详细介绍
4).getPriorit()
获取线程的优先级
5).isDaemon()
判断线程是否为后台线程(守护线程)
前台线程,会阻止线程结束,前台线程的工作没做完,进程是无法结束的.
后台线程,不会阻止进程结束,后台线程的工作没做完,进程也是可以结束的.
在代码中创建的线程都是前台线程,包括main默认也是前台的,其他的jvm自带的线程都是后台的,也可以使用 setDaemon() 方法,将前台线程设置为后台线程.
6).isAlive()
判断当前系统中的这个线程是不是真的存在.
在调用start之前,调用isAlive就是false
在调用start之后,调用isAlive就是true
新线程的run方法还没执行,isAlive()就是false
新线程的run方法正在执行,isAlive()就是true
新线程的run方法执行结束,isAlive()就是false
7).isInterrupted()
判断线程是否被中断,后面会详细介绍
🐭2.线程的中断(终止)
这里的中断,并不是直接让该线程中断,而是通知这个线程:你该中断(终止)了.但是最终是否要终止取决于代码的具体写法.
举例: 博主正在沙发上玩手机,然后老妈告诉在厨房说了一句:儿子,你去把垃圾倒了.此时对于博主而言,有三种选择:
a.立即停止玩手机,直接去倒垃圾.
b.再玩会手机,一会去倒垃圾.
c.假装没听见,不去倒垃圾.
对于线程的终止有如下两种操作
1).使用标志位来控制线程是否要终止.
此时就可以在主线程中,通过修改flag的值来决定t线程是否结束.运行结果如下
自定义变量这种方式不能及时响应,尤其是在sleep休眠时间比较长的时候.
2).使用Thread自带的标志位进行判定
如下代码:
这里的t.interrupt(); 就是终止t线程(在main线程中被调用,是main通知t线程终止)
此时,运行这段代码,观察运行后的结果
当运行后,会发现,虽然这里报了异常,但是程序还是在执行,这是为什么呢?
这是因为:interrupt做了两件事
a.把线程内部的标志位(boolean)给设置成true.
b.如果线程在进行sleep,就会触发异常,把sleep唤醒.
但是,当sleep被唤醒后,sleep还会做一件事,将刚刚设置的这个标志位,再设置回false,
清空了标志位,这就导致了,当异常被catch块捕捉后,循环还要继续执行,所以程序并没有结束.
所以说,调用interrupt,只是通知终止,是否真的终止取决于线程自己(具体的代码)
通过下面三组代码可以更好的理解上面这段话.
为什么sleep要清除标志位呢?
这是因为,当线程被唤醒后,到底是要终止,还是不要终止,是立即终止还是稍后终止,就将选择权交给了程序猿自己.
🐭3.线程的等待
等待线程:就是控制两个线程的执行顺序.
如下代码:
运行代码,查看结果:
从运行结果可以看出,当start创建一个新线程后,main线程和t线程继续往下执行,先执行了main线程的打印操作,然后main线程等待t线程先执行,等到t线程执行结束后,再往下执行.main线程等待t线程的时候发生了阻塞(block,常见术语).
t线程肯定是比main线程先结束的.
如果是执行join的时候,t已经结束了,会出现什么情况呢?
此时观察运行结果,会发现,当5s后,main线程立即往下执行,并不会阻塞,这就说明
如果执行join的时候,t线程已经结束, 此时join不会阻塞,会立即返回.
join方法
第一个不带参数,表示线程A死等线程B,如果线程B不执行结束,线程A一直等
第二个带参数,参数指定一个超时时间(最大等待时间),当线程A等待线程B执行时间超过这个时间的话,线程A不会继续等待线程B,线程A开始执行.
举例:第一种情况
你和你女朋友要出去约会,你女朋友告诉你让你在她家楼下等她,如果见不到女朋友就一直等,'不见不走'.
第二种情况:
你和你女朋友要出去约会,你女朋友告诉你让你在她家楼下等她,你给自己定个等的目标(30min),超过30min就不等了,直接走.(当然这只是一个例子,等还是要等的)
🐭4.获取线程实例
通过静态方法:Thread.currentThread() 返回当前线程对象的引用.
结合getName() 方法可以观察:
🐭5.线程的休眠
让线程休眠,本质上就是让这个线程不参与调度了(不去CPU上执行了)
在操作系统内核中,有这样一些PCB,构成如下链表:
在这个链表中的PCB都是就绪状态,这个链表称为就绪队列
PCB并不是使用一个简单的链表来组织的,而是以一系列以链表为核心的数据结构来组织的.
操作系统每次需要调度一个线程去执行,就从就绪队列中选一个就好.
此时,当线程A调用sleep,就会进入休眠状态,将线程A从上述链表中取出,放到另外一个链表中,如下:
在这个链表中的PCB都是阻塞状态,暂时不参与CPU的调度执行,将这个链表称为阻塞队列
一旦线程进入阻塞状态,对应的PCB就进入阻塞队列了,此时就暂时无法参与调度了.
🐕二、线程的状态
🐍1.介绍
状态是针对当前线程调度的情况来描述的,状态是线程的一种属性,java对于线程的状态进行了细化,主要有以下6种:
1).NEW
创建了Thread对象,还没调用start,操作系统内核中还没创建对应的PCB
代码观察:
2).TERMINATED
操作系统内核中的PCB执行完毕,线程执行结束,但是Thread对象还在
代码观察:
一旦线程PCB消亡了,此时代码中的t1对象也就没什么用处了,之所以t1对象还存在,是因为java中的对象的生命周期和操作系统内核里的线程并非完全一致,内核的线程释放的时候,无法保证java代码中的t1对象也立即释放,因此,就会存在内核中的PCB没了,但是代码中的t1对象还存在这样的情况,所以就需要通过特定的状态来把t1对象标识为无效.
3).RUNNABLE
可运行的,包括正在CPU上运行的以及在就绪队列中等待去CPU上运行的
代码观察:
之所以这里能看到RUNNABLE 是因为当前线程run方法里面没写任何sleep之类的方法,当run方法中加上sleep方法时,就可能看到TIMED_WAITING 和 RUNNABLE交替出现,主要出现TIMED_WAITING 还是 RUNNABLE 就取决于t线程运行到哪个环节了.
4).TIMED_WAITING
阻塞状态
观察代码:
通过这里的循环,就能看到这里的交替状态了,当前获取到的状态取决于获取状态的这一瞬间,t1线程处在哪种状态(正在执行,还是sleep)
5).WAITING
阻塞状态
代码观察:
这里的synchronized 是加锁操作,后续会介绍,这里观察线程的状态即可,t2线程调用wait方法后就处于WAITING状态.
6).BLOCKED
阻塞状态
🐍2.状态之间的切换条件