目录
📕 Thread类的概念
📕 Thread 的常见构造方法
📕 Thread 的几个常见属性
📕 start()-启动一个线程
📕 中断一个线程
🚩 实例一
🚩 实例二
🚩 实例三
📕 join()-等待一个线程
🚩获取当前线程引用
🚩休眠当前线程
📕 Thread类的概念
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理
📕 Thread 的常见构造方法
前面两个构造方法上述在前面已经讲过,第三个和第四个构造方法是可以在创建线程的时候,给线程起个名字,是否起名字对于线程运行的效果并没有影响,好处是Java运行过程中,可以看到每个线程的名字,出问题的时候更直观的把出现的问题和代码联系起来(方便调试),通过jconsole / IDEA调试器都能看到线程名字,若没起名字,也有默认的名字Thread-0,Thread-1,Thread-2....
取名:
📕 Thread 的几个常见属性
常见属性说明:
-
ID 是线程的唯一标识,不同线程不会重复,这里的id和pcb的id是不同的,是jvm自己搞的一套体系,Java代码也无法获取到pcb的id
-
名称是各种调试工具用到
-
状态表示线程当前所处的一个情况,后面会一一介绍
-
优先级高的线程理论上来说更容易被调度到,但是虽然Java提供了优先级接口,实际上修改了优先级接口现象也不是很明显,你修改的优先级是一回事,系统调度又是另一回事,你这里的优先级只能是一个"建议参考",具体还得以人家自身为准
-
关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
例子:
将 t 线程设为后台线程:此时进程中只有main是前台进程了,只要main结束,整个进程就结束了,main执行完 start 立即结束,此时 t 线程还没有来得及打印,进程就结束了,里面的线程也自然随之结束了,所以什么也打印不出来。
注意:此处也有一定的概率出现 t 打印一次,然后进程结束的情况,就是看main先执行结束还是t还执行一次(线程之间是抢占式执行,调度顺序不确定),但是按照经验来看,当前代码结构中,大概率是什么都不打印,即使你尝试运行1w次,结构可能都是什么都不打印,但是不能保证1w零1次是否打印!!!概率不均等的原因是main调用start的速度很快,对于 t 来说,要把线程创建出来之后才会执行打印,但是创建的本事有时间开销,虽然比进程的创建轻量,也不是为0,相对于main来说要更慢!!!
-
是否存活,即简单的理解,为 run 方法是否运行结束了
指的是系统中的线程(pcb)是否还存在,Thread对象的声命周期和pcb的生命周期不一定完全一样
例子:
更改:
正因为Thread类和系统中的线程生命周期不一致,因此就可以通过上述谈到的 isAlive 方法,判定系统中的线程是否仍然存在
判断出当前这个线程是否存货(指的是内核中的线程,与对象本身没有关系),此时现在进程已经结束了,得到的结果为false。
当我们把线程中睡眠加上,因为线程中是sleep(2000),下面是sleep(1000),进行打印的时候try—>catch中的还没有睡醒,即线程还存在,所以结果为true。
-
线程的中断问题,下面会进一步说明
上述属性中名称,后台线程,是否存货了解的重点!!!
注意,上述属性一定是写在 start 之前的!!!
📕 start()-启动一个线程
start 才是正在的创建线程(在内核中创建pcb),一个线程需要通过run/lambda把线程要执行的任务定义出来,start 才是正在的创建线程,并开始执行
核心就是是否真正的创建线程出来,每个线程都是独立调度执行的(相当于整个程序中多了一个执行流,即多了一个while死循环,并发执行)
一个 Thread 对象只能 start 一次
要想在搞一个新的线程,就需要要创建另一个 Thread 类对象
📕 中断一个线程
与其叫中断,不如说终止,中断整个词在计算机中包含很多意思。
🚩 实例一
1,自己实现,控制线程结束的代码例子
我们不在while中写true,自己创建一个boolean类型的变量来作为while的条件,让其3s之后,修改变量的值来结束 t 线程,这样让main线程去决定 t 线程结束这样的效果,所谓让 t 线程结束,就是让线程的入口方法执行完毕,对于while来说就是结束循环即可
上述代码,我们是把isRunning定义成的一个成员变量,若把他定义成一个局部变量,代码就会直接编译报错,
如果上述代码 t 线程是睡眠10s,甚至更长,此时的main线程是无法及时的把 t 线程终止掉,就有 Thread 类提供的方法来实现。
🚩 实例二
2,使用 Thread 类提供的interrupt方法和 isInterruptted方法,来实现上述效果
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记
刚才是自己定义了一个boolean变量,实际上Thread里面内置了一个,使用内置的标志位,功能更强大!!!
🚩 实例三
观察标志位是否清除
所以可以认为interrupt方法,线程中有sleep,就会唤醒sleep,若代码没有执行到sleep,就还是一个单纯的设置标志位。
📕 join()-等待一个线程
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。
例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束
例子:
由于线程是"抢占式"执行且并发执行,所以谁先结束每次都是不确定的,如果希望让代码里面的 t 先结束,main后结束,就可以在main中使用线程等待(join)。
可以认为谁调用jion谁就阻塞,比如代码中有main线程,又有t1和t2线程,此时main调用t1.join,mian就阻塞,然后t1和t2并发执行,如果t1打印比t2打印的时间短,那么此时t1和t2一起打印,当t1打印完之后,t2还没有打印完,main线程已经回到就绪态,那么t2就和main一起打印。
在main线程中调用两个join:
t1和t2之间相互等待:
此时的代码就是t1等待t2,main线程又在等待t1
还可以其他线程等待main:(非常规写法)
上述只是join无参数版本的,也就是死等,只要 t 不结束,就会一直等待下去,还要带参数的版本
在实际开发中,一般很少使用死等这个策略
传入一个时间:传入的时间是最大等待时间,比如写的等待10s,如果10s之内,t 线程结束了之间返回,如果10s到了,t 线程还没有结束不等了!!!继续往下走。(第三种方法纳秒级别的时间,对于主流系统来说,更精细了会导致误差)
🚩获取当前线程引用
这个方法我们以及非常熟悉了,前面都已经使用过了,就不做过多赘述了
🚩休眠当前线程
也是我们比较熟悉一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
sleep方法是native修饰的,底层是C++代码写的