Thread类
Thread类
概述
一个线程就是一个“执行流”。每个线程都可以按照顺序执行自己的代码,而多个线程可以“同时”执行多份代码。线程本身是操作系统中的概念。操作系统内核实现了线程这样的机制,并且对用户层提供了一些API供用户使用(例如Linux
的pthread
库)。而Java标准库中的Thread
类可以视为是对操作系统提供的API进行了进一步的抽象和封装
Thread类是JVM
用于管理线程的一个类。在Java
中每个线程执行流都是通过Thread类的对象来描述。JVM
会将这些Thread对象组织起来,用于线程调度、线程管理
使用Thread
类,我们可以创建和管理多个线程,并且控制它们的执行顺序、优先级、暂停、恢复等
Thread的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否位后台线程 | isDaemon() |
是否存活 | isAlive() |
是否打断 | isInterrupted() |
- ID是线程的唯一标识,不会重复
- 名称就是线程的命名
- 状态标识线程当前所处的一个情况
- 是否存货,可以简单地理解为:
run
方法是否运行结束了 - 优先级高的线程理论上来所更容易被调度到,但并不绝对。因为优先级只是”建议“性质的,不是强制性的。优先级意味着”建议“操作系统优先调度某线程,但实际上到底要不要优先调度,还是取决于操作系统
- 线程分后台线程和前台线程。关于后台线程:
JVM
会在一个进程的所有非后台线程结束后,才会结束运行。创建的线程默认是前台线程,main
线程也是一个前台线程
1 |
|
常用方法
启动线程
start()方法
start()方法是启动线程的方法,调用该方法会使线程进入就绪状态,等待CPU分配时间片后开始执行。run()方法是线程的执行体,它包含了该线程要执行的代码
当调用start()方法启动线程后,线程会在独立的执行路径下自动执行run()方法中的代码
主要注意的是:run()方法并不标识新的线程的创建;调用start方法,才真的在操作系统的底层创建出一个线程
1 |
|
线程中断
方法1-成员变量
1 | public static boolean quit=false; |
为什么quit要声明为成员变量?
在Java中,Lambda表达式内部访问的局部变量必须是final或者事实上的final(不对其进行修改),这是因为Lambda表达式实际上是一个闭包,它包含了对外部的变量的应用。这些变量在Lambda表达式执行期间不能被修改,避免线程安全问题确保数据的一致性
闭包是一个函数及其相关的引用环境,它可以自由地访问其所在的词法作用域中的变量。简单来说,闭包是一种函数,它可以捕获外部作用域中的变量,并在其生命周期内继续使用这些变量,即使变量已经离开了作用域。
方法2-Interrupt
isInterrupted()
就可以理解为,是t对象自带的一个结束标志位。通过t.interrupt()
方法将t内部的标志位给设定成 true
。
打断线程,但线程停不停下来由自己决定
interrupt()
方法不仅会改变线程内部的标志位,还会将sleep()
唤醒。这是上述异常来源的原因。
1 | public static void main(String[] agrs){ |
interrupt()方法的作用
- 设置标志位为true
- 如果该线程正在阻塞状态中(如正在执行sleep,join,wait等),此时就会把该线程从阻塞状态唤醒,并通过抛出异常的方式让sleep立即结束。
换句话说,如果interrupt()执行时,t线程正在sleep,那么interrupt()在将标志位设置为true后,又会直接将sleep强行唤醒。sleep在该程序中,占据了绝绝绝大部分的时间,因此当interrupt()执行时,几乎一定会遇到正在sleep的情况。注意:当sleep被提前唤醒的时候,sleep会自动把isInterrupted标志位再次清空(即把true又变为false)。这就导致了下次再判断循环条件,循环条件还成立,因此循环还在继续执行。
线程等待
线程之间是并发执行的,操作系统对于线程的调度是无序的,无法判断两个线程的执行先后。
join()方法
- 在一个线程中调用另一个线程的join()方法就是让当前线程阻塞,等待另一个线程的执行结束
- 带参数,就是最多等待的时间