文章目录
前言
本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果这篇文章可以帮助到你,劳请大家点赞转发支持一下!
多线程基础篇2来喽,本篇文章带你了解Thread类的常见属性与方法以及线程的六种状态。
一、Thread类
1️⃣Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
2️⃣每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象
就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
Thread 的常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread(Runnable target, String name)
与Thread(String name)
,只不过是创建新线程的方法不同,name的作用是相同的😉😉。
以上构造方法除了命名其他都在多线程基础1中都使用过了。
所以这里就介绍一下😲😲😲这个命名Thread(Runnable target, String name)
。
在你jdk的文件夹中有一个可以监视线程的程序
点开就可以在本地进程中看到正在执行的线程😏😏😏。
咱们运行下面这段代码,创建并运行一个新线程
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true) {
System.out.println("hello thread");
}
});
thread.start();
}
}
选中创建的新线程,点击连接。
如果点击连接后,遇到如下情况,直接点击不安全的连接即可。
🙁🙁🙁因为咱们只是用来测试的,并没有任何价值。
假如我们没有给创建的新线程命名,那么系统会自动为新线程命名Thread-0,Thread-1,按递增顺序一直排下去。
红框是线程名
蓝框中是选中的线程的一些信息
🤔🤔🤔此时大家一定很好奇,为什么没有看到主线程main线程???
原因是他在我们查询线程之前就已经执行完毕,结束了。
我们给主线程也加一个死循环看一下。
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true) {
System.out.println("hello thread");
}
});
thread.start();
while (true) {
System.out.println("hello main");
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true) {
System.out.println("hello thread");
}
},"我的新线程");
thread.start();
}
}
这就是我们给线程起的新名字,主要是方便以后改多线程程序的bug😅😅😅,
多线程程序是不好调试的,因为你即使打断点了,其他线程仍在执行,就造成了与执行情况的误差😓😓😓。
Thread 的一些常见属性
属性 | 属性说明 | 获取方法 |
---|---|---|
ID | ID 是线程的唯一标识,不同线程不会重复 | getId() |
名称 | 名称是各种调试工具用到 | getName() |
状态 | 状态表示线程当前所处的一个情况,下面我们会进一步说明 | getState() |
优先级 | 优先级高的线程理论上来说更容易被调度到 | getPriority() |
是否为后台线程 | 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行 | isDaemon() |
是否存活 | 是否存活,即简单的理解,为 run 方法是否运行结束了 | isAlive() |
是否被中断 | 线程的中断问题,下面进一步说明 | isInterrupted() |
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 10; i++) {
// 获取当前线程引用,文章后面会讲解
try {
System.out.println(Thread.currentThread().getName() + "仍存活");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "将要死去");
},"Thread");
System.out.println(thread.getName() + "的ID :" + thread.getId());
System.out.println(thread.getName() + "的名称 :" + thread.getName());
System.out.println(thread.getName() + "的状态 :" + thread.getState());
System.out.println(thread.getName() + "的优先级 :" + thread.getPriority());
System.out.println(thread.getName() + "是否为后台线程 :" + thread.isDaemon());
// 前台线程会阻止java进程结束
// 必须所有前台线程执行完毕,java进程才能结束
// 创建的线程默认是前台线程
// 可以通过setDaemon方法设置成后台线程
System.out.println(thread.getName() + "是否活着 :" + thread.isAlive());
// true表存活,false表死亡
System.out.println(thread.getName() + "被中断 :" + thread.isInterrupted());
thread.start();
while (thread.isAlive()) {
try {
System.out.println(thread.getName() + "的状态 :" + thread.getState());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
二、Thread的常用操作
1.启动一个线程
重写run方法,包括new出一个线程,只是创建出了一个线程对象,但是这不意味着线程真的创建出来并开始执行了。
1️⃣ 重写run方法,就好比将工厂里的生产线图纸画好了。
2️⃣ new出一个线程,就好比将生产线的各个零部件安排到位了。
3️⃣ 执行start方法,就好比将这些零部件组装成生产线,并开始独立运行生产线。
调用 start 方法, 才真的在操作系统的底层创建出一个线程
2.休眠一个线程
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis毫秒 |
public static void sleep(long millis, int nanos) throwsInterruptedException | 可以更高精度的休眠 |
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true) {
try {
long a = System.currentTimeMillis();
Thread.sleep(3*1000);
long b = System.currentTimeMillis();
System.out.println(b-a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
sleep方法是让线程休眠的方法,
因此如果线程在休眠时被唤醒了,就会抛出InterruptedException异常🤐🤐🤐。
因此sleep方法会有一个必须要处理的受查异常,
这里咱们使用try catch语句来处理。
因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。(线程睡醒后,再去被cpu调度,因此可能睡醒后需要等待一段时间,才能被执行,所以前后时间差大于等于3000毫秒)
3.获取当前线程引用
方法 | 说明 |
---|---|
public static Thread currentThread() | 返回当前线程对象的引用 |
public class ThreadDemo {
public static void main(String[] args) {
//在哪个线程中使用就返回哪个线程对象的引用
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
4.等待一个线程
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
1️⃣无参数版
join(死等)
//join方法也有受查异常,这里使用声明异常处理
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "结束工作");
},"张三");
Thread thread2 = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "结束工作");
},"李四");
System.out.println("先让张三工作");
thread1.start();
//在main线程中调用的thread1的join
//效果就是main方法会等待thread1线程执行完,才会继续执行
thread1.join();
System.out.println("张三工作完了");
System.out.println("李四开始工作");
thread2.start();
//在main线程中调用的thread2的join
//效果就是main方法会等待thread2线程执行完,才会继续执行
thread2.join();
System.out.println("李四工作完了");
}
在a线程中调用b线程的join方法,
a线程走到join方法
这一行代码后,a线程不再参与CPU调度(进入阻塞状态),等待b线程执行完,才继续去参与CPU调度(解除阻塞状态),去执行下面的语句。
2️⃣有参数版
join(参数为等待的最大时间,超过最大时间,无论那个线程有没有执行完,都不再等待)
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true) {
System.out.println("thread线程正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"thread");
thread.start();
try {
thread.join(3000);
System.out.println("我已经等了Thread线程3000毫秒了,不等了,我要去排队执行了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
主线程等待了thread线程3000毫秒后,开始去排队执行,所以算上排队的时间,前后时间差仍会大于3000毫秒
关于 join 还有一些细节内容,我们留到后续文章再讲解。
5.中断一个线程
目前常见的有以下两种方式:
1️⃣通过共享的标记来进行沟通
2️⃣调用 interrupt() 方法来通知
1️⃣通过共享的标记来进行沟通
public class ThreadDemo1 {
//后续再为大家介绍volatile关键字
public static volatile boolean isQuit = false;
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (!isQuit) {
//currentThread方法是获得当前线程的引用
System.out.println(Thread.currentThread().getName() + "正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "结束工作");
});
System.out.println(Thread.currentThread().getName() + "控制生产线开始工作");
thread.start();
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
System.out.println(Thread.currentThread().getName() + "控制生产线停止工作");
}
}
值得注意的是此处的标记必须是成员变量,不能是局部变量。
因为Lambda表达式的变量捕获规则。
Lambda表达式及其变量捕获
2️⃣调用 interrupt() 方法来通知
使用 Thread.interrupted()
或者 Thread.currentThread().isInterrupted()
代替自定义标志位。(Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记)
方法 | 说明 |
---|---|
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位 |
public static booleaninterrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public booleanisInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
- 使用 thread 对象的 interrupt() 方法通知线程结束。
public static void main(String[] args) {
Thread thread = new Thread(()->{
//如果标志位被设置成true那么就会跳出while语句
while (!Thread.currentThread().isInterrupted()) {
System.out.println("标志位未被设置");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//设置标志位为true
thread.interrupt();
}
这里就只抛出了一个异常,然后该进程仍继续执行???
和预期的不一样啊🤔🤔🤔!!!
说明标志位没有被设置成true,why???
先来分析 interrupt方法
作用
线程正在CPU上执行
,设置标志位为true线程正在阻塞中
,(比如调用wait/join/sleep 等方法而阻塞挂起),通过抛出异常的方式,让这些方法立刻结束。
问题就出在这里 wait/join/sleep 等方法结束时,会自动的把标志位清空
(true->false)
。
😲😲😲这就导致循环仍然可以继续。
如果sleep刚执行完,设置的标志位,那么循环就可以结束了。
不过这种概率非常低,因为CPU的执行效率非常之高,一秒钟可以执行调度上千上万次,所以相对来说sleep的时间就占据整个循环体99.99%的时间,因此大多数时间该线程都在sleep。
总结来说:
- 如果标志位是false,sleep正常执行休眠操作。
- 如果标志位是true,无论此时,sleep刚开始执行,还是执行了一半,都会做出两个反应,1.立即抛出异常 2.清空标志位为false。
因此interrupt方法只是通知线程要结束了,而不是命令线程立刻结束。
可以通过代码去灵活的控制线程是否结束。
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
while (!Thread.currentThread().isInterrupted()) {
System.out.println("标志位未被设置");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 利用try-catch语句来使循环结束
System.out.println("线程结束了");
// 通过break来跳出循环
break;
}
}
});
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
三、线程的状态
操作系统中的线程,自身是带有一个状态的。
但是Java Thread是对系统线程的封装,把这里的状态又进一步的精细化了。
观察线程的状态
线程状态 | 说明 |
---|---|
NEW | 创建了线程的引用, 但是没有让该线程开始工作 |
RUNNABLE | 就绪状态(正在执行状态或可执行状态) |
BLOCKED | 等待获取锁 |
WAITING | 线程在无限等待唤醒 |
TIMED_WAITING | 线程在等待唤醒,但设置了最长等待时限 |
TERMINATED | 线程执行完毕,结束了 |
线程的状态是一个枚举类型 Thread.State
1️⃣NEW状态
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("hello");
});
// 线程创建好,但未执行
System.out.println(thread.getState());
}
2️⃣RUNNABLE状态
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
// 空循环
}
});
thread.start();
// 线程正在执行,或可以随时去CPU上执行
System.out.println(thread.getState());
}
3️⃣TIMED_WAITING状态
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程在sleep时,是TIMED_WAITING状态
System.out.println(thread.getState());
}
绝大多数时间,线程都是在sleep的。
4️⃣TERMINATED状态
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("hello");
});
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程执行完毕,Thread对象还存在
System.out.println(thread.getState());
}
BLOCKED状态
与WAITING状态
,需要用到后续知识,在此先不做讲解,
等后续文章更新后,再回来讲解。
线程状态之间的转换
每个线程分为三个主要阶段
1️⃣当线程创建完成时,就进入到了阶段1
2️⃣线程调用了start方法后,便开始了阶段2
阶段2,会根据代码中使用的线程方法,在这几个状态中来回切换。
3️⃣当线程执行完毕后,进入阶段3
。
上面提到的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。
总结
以上就是今天要分享的多线程内容了,多线程还有很多后续知识要与大家分享,期待下一篇文章见!!🥰🥰🥰
路漫漫,不止修身也养性。