1、Thread类
1.1 Thread类的作用
上篇博文中我们了解了线程与操作系统的关系:线程是操作系统中的概念,操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用,Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装
既然Thread类可以视为对操作系统提供的API进行了进一步的抽象和封装,那么我们就可以通过Thread类来操作线程
1.2 Thread类常见的构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |
1.3 Thread几个常见属性
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority |
是否后台线程(守护线程) | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
守护线程和非守护线程:
是否是“后台线程”也称为 是否是“守护线程”
是否是“守护线程”,可以分为:
-
后台线程(守护线程),不会阻止进程的结束,即使后台线程的工作没有做完,进程也是可以结束的
-
前台线程(非守护线程),会阻止进程的结束,如果前台线程的工作没有做完,进程是不能结束的
我们平时手动创建的线程,默认都是前台线程,包括 main 线程在内
jvm自带的线程都是后台线程
我们通常是可以通过手动将前台线程设置为后台线程的,设置方法 setDaemon
2、 启动一个线程
public class Demo {public static void main(String[] args) {Thread thread = new Thread(()->{//任务描述System.out.println("hello run");});//线程创建thread.start();}
}
对象名.start():开启一个线程
如果光创建一个Thread对象,不进行start,操作系统内核中是否有线程呢?
Thread thread = new Thread(()->{//任务描述System.out.println("hello run");
});
答:没有,只有 对象名.start() 才会在让内核创建一个 PCB,此时这个 PCB 才表示一个真正的线程
-
当创建了一个 Thread 对象,并没有调用 start 方法之前,如果调用 isAlive 就是 false
-
调用 start 方法后线程正在执行,如果调用 isAlive 就是 true
-
如果内核里线程把 run 执行完后,此时线程就会销毁,pcb也就会随之释放,那么此时 isAlive 就是 false
isAlive 是在判断,当前系统里面的这个线程是不是真的存在
3、 中断一个线程
中断不是让线程立即停止,而是通知线程,你应该要停止了。是否真的停止,还是需要取决于线程这里的具体代码的写法
3.1 如何中断线程
3.1.1 使用标志位来控制线程是否要停止
public class ThreadDemo {public static Boolean flag = true;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while (flag) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);//抛出异常}}});thread.start();Thread.sleep(3000);flag = false;}
}
自定义变量这种方式,不能及时响应,尤其是在 sleep 休眠的时间比较久的时候
这个代码就是修改 flag,让thread线程结束,thread线程是否真的结束完全取决于thread线程内部的代码,代码里通过flag控制循环的。因此这里只是告诉这个线程结束,这个线程是否要结束以及啥时候结束都是线程内部自己代码来决定了
3.1.2 使用Thread自带的标志位,来进行判断
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();//打印出异常堆栈信息}}});thread.start();Thread.sleep(3000);thread.interrupt();//终止线程}
}
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位 |
public static boolen interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清楚标志位 |
public static Thread currentThread() | 返回当前线程对象的引用 |
上述代码的运行结果:
上述这个异常是调用interrupt触发的,那为什么异常被触发后,线程依旧在执行咧?
调用 interrupt 会做两件事:
-
把线程内部的标志位设置成 true
-
如果线程在进行 sleep,会触发异常,把sleep唤醒。唤醒的时候,把刚才设置的这个标志位,在设置回 false(清空标志位)
3.2 中断线程的情况
上述我们提到了中断不是让线程立即停止,而是通知线程,你应该要停止了。是否真的停止,还是需要取决于线程这里的具体代码的写法
举例说明:
3.2.1 线程Thread忽略终止请求
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();//打印异常信息,不做其他处理}}});thread.start();Thread.sleep(3000);thread.interrupt();}
}
3.2.2 线程 Thread 稍后响应终止请求
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();//等待0.5s后,线程终止try {Thread.sleep(500);} catch (InterruptedException ex) {ex.printStackTrace();}break;}}});thread.start();Thread.sleep(3000);thread.interrupt();}
}
3.2.3 线程Thread立即响应终止请求
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;//线程直接终止}}});thread.start();Thread.sleep(3000);thread.interrupt();}
}
4、等待一个线程 join
等待一个线程:对象名.join( )
线程是一个抢占式执行,随机调度的过程。等待线程要做的事情,就是控制两个线程的结束顺序
方法 | 说明 |
public void join() | 一直等,直到线程结束 |
public void join(long millis) | 等到一定时间 |
public void join(long millis,int nanos) | 等到一定时间,但可以更高的精度 |
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 100; i++ ) {System.out.println("hello");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();thread.join();//join:main线程等待thread线程结束之后在往下执行main线程剩下的内容for (int i = 0; i < 100; i++) {System.out.println("main");Thread.sleep(1000);}}
}
代码解析:本身执行完 start 之后,thread 线程和 main 线程就并发执行,分头行动。main 继续往下执行,thread也会继续往下执行,当main遇到thread.join就会发生阻塞,一直阻塞到thread线程结束,main线程才会从 join 中恢复回来,才能继续往下执行。
经过 join 的控制,thread 线程肯定会比main 线程先结束
问题:假设执行 join 的时候,thread 线程已经结束了,此时的 join 会咋样?
答:如果执行 join 的时候,thread 已经结束了,join 就不会阻塞,会立即返回
5、休眠当前的线程
我们通常采用 sleep 让线程休眠,休眠的本质就是让这个线程不参与调度(也就是不去cpu上执行),等到休眠时间到了在参与调度
操作系统内核:
就绪队列:这个列表里面的PCB都是“随叫随到”,就绪状态
当就绪队列中的线程 A 调用 sleep 就会进入休眠的状态,把 A 从上述就绪列表中拎出来,放在另一个链表中,这个链表中的PCB,都是“阻塞状态”,暂时不参与 CPU 的调度执行
一旦线程进入阻塞状态,对应的PCB就进入阻塞队列了,此时就暂时无法参与调度了
只有对应的PCB回到就绪状态,才会重新加入调度序列,一般在唤醒之后不会立即执行,因为考虑调度的开销
PCB 是使用链表来组织的,实际上并不是一个简单的链表,而是一系列以链表为核心的数据结构
6、线程的状态
线程的状态是根据线程的调度情况来进行描述的 ,在Java中对线程的状态进行了如下细化:
- 初始状态(NEW):线程被创建后,就处于这个状态。这时,它还没有开始运行。
- 运行状态(RUNNABLE):当线程被启动后,就转为运行状态。这时,它正在Java虚拟机中运行。需要注意的是,即使线程处于运行状态,也不一定意味着它正在执行CPU计算。
- 阻塞状态(BLOCKED):如果线程需要等待监视器锁才能继续执行,它就会进入阻塞状态。例如,当一个线程试图获取一个内部的对象锁,而这个锁被其他线程持有,那么这个线程就会变成阻塞状态。
- 等待状态(WAITING):进入等待状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待状态(TIMED_WAITING):这是等待状态的一种特殊形式,线程在此状态下会等待特定的时间间隔。
- 终止状态(TERMINATED):当线程完成执行或因异常而结束时,就会进入这个状态。
举例说明:假设你想上厕所,首先是你得想上厕所(相当于NEW状态) ,所以你得去厕所上,进入厕所后你才能上(相当于RUNNABLE状态),当你还未进入厕所时可能你女朋友在里面上厕所把门锁上了,所以你得等,等她上完把门打开你才能进去上厕所(相当于BLOCKED状态),还有可能你女朋友上完后厕所有点臭,你想等个两分钟后等味道散了在去上(相当于TIME_WAITING状态),还有可能厕所坏了,你得等修厕所的师傅修好,跟你说了你才能上(相当于WAITING状态),等你上好后(相当于TERMINATED状态)
注:一个线程对象只能 start 一次
这里主要给大家演示 NEW、RUNNABLE、TERMINATED :
public class Demo {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();//创建一个线程System.out.println("状态" + t.getState());t.start();//启动一个线程Thread.sleep(2000);System.out.println("状态" + t.getState());t.interrupt();//中断一个线程Thread.sleep(2000);System.out.println("状态" + t.getState());}
}
class MyThread extends Thread {@Overridepublic void run() {while (!Thread.currentThread().isInterrupted()){System.out.println("hello thread");try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}}
}