线程的基本操作以及线程的状态

本文详细介绍了Java中线程的基本操作,如线程的创建、中断、等待和休眠等,并探讨了线程的状态及其切换条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

🐳今日良言:得之坦然,失之淡然,争取必然,顺其自然。

🐯一、线程的基本操作

🐭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.状态之间的切换条件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值