多线程篇二

如笔者理解有误,欢迎交流指正⭐

Thread中重要属性

启动线程start

上篇提到 **start 则是真正调用了系统 API, 在系统内核中创建出线程, 让线程再调用 run.**(“并发“的实现)
再用代码举例下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 创建一个类, 继承自 Thread .
class MyThread extends Thread {
@Override
public void run() {
// 这个方法就是线程的入口方法.
while (true) {
System.out.println("Welcome Thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 创建线程.
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
//thread.start(); 交替打印
thread.run();
//全部打印Welcome Thread!
while (true) {
System.out.println("Hello Main!");
Thread.sleep(1000);
}
}
}

终止(中断)线程

顾名思义,isInterrupted存在的意义就是为了帮助我们停止正在运行的线程.

为什么要中断线程?

作为一个独立的单位,做一件事一定是有目的而且大多结果都是对自己有利,如果局面逐渐不可控制,甚至会造成损害那就有必要让其停下来或者直接取消对应的计划.
在java中我们引入中断的目的就是未了打断线程所处的某种状态(这种状态一定是阻塞状态)

中断的实现

要终止/销毁线程,就是要想办法让run方法尽快执行结束.

手动创建标志位

我们可以在代码中手动创建标志位,作为run执行结束的条件.
上代码套餐.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 线程的打断
public class Demo8 {

public static void main(String[] args) throws InterruptedException {
boolean isQuit = false;

Thread thread = new Thread(() -> {
while (!isQuit) {
// 此处的打印可以替换成任意的逻辑来表示线程的实际工作内容
System.out.println("线程工作中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程工作完毕!");
});

thread.start();
Thread.sleep(3000);

isQuit = true;
System.out.println("设置 isQuit 为 true");
}
}

运行发现image-20240816205907546

为什么出现这样的情况?
小小lambda 大大的力量😀
isQuit此时是成员变量,而lambda有一个语法规则”变量捕获“.

变量捕获

lambda表达式会把当前作用域中的变量在lambda中cv一份(外面的变量是否销毁也就无关紧要了)
注意:变量捕获的前提是必须只能捕获一个final或者”实际上是final“的变量.(即初始化赋值后未改变值内容的“变量”)
image-20240816205923769
所以isQuit为成员变量时lambda捕获不到,就变成了“内部类访问外部类的属性”.就没有final的限制了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 线程的打断
public class Demo8 {
private static boolean isQuit = false;

public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread(() -> {
while (!isQuit) {
// 此处的打印可以替换成任意的逻辑来表示线程的实际工作内容
System.out.println("线程工作中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程工作完毕!");
});

thread.start();
Thread.sleep(3000);

isQuit = true;
System.out.println("设置 isQuit 为 true");
}
}
缺点

1.需要手动创建变量.
2.当线程内部sleep时,主线程修改变量,新线程不能及时响应.

调用interrupt()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 线程终止
public class Demo9 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
// Thread 类内部, 有一个现成的标志位, 可以用来判定当前的循环是否要结束.
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程工作中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();

try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("让 t 线程终止. ");
thread.interrupt();
}
}

currentThread()获取当前线程的实例(Thread thread)
Thread内部有一个标志位用来判断线程是否结束
thread.interrupted()将Thread对象内部的标志位置为true(即使线程内部出现逻辑阻塞【sleep】也可以使用interrupted唤醒【使sleep内部触发一个异常 被提前唤醒】)
运行发现

image-20240816183840344

sleep确实被唤醒了但是线程仍在工作,并未真正结束.
注意:interrupted唤醒线程之后 ,sleep方法抛出异常,同时会自动清楚刚才设置的标志位
有三方式可以让我们有更多的“操作空间”.
上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 线程终止
public class Demo9 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
// Thread 类内部, 有一个现成的标志位, 可以用来判定当前的循环是否要结束.
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程工作中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 1. 假装没听见, 循环继续正常执行.
e.printStackTrace();
// 2. 加上一个 break, 表示让线程立即结束.
// break;
// 3. 做一些其他工作, 完成之后再结束.
// 其他工作的代码放到这里.
break;
}
}
});
thread.start();

try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("让 t 线程终止. ");
thread.interrupt();
}
}

附:

方法 说明
public void interrupt() 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位
public static boolean interrupted() 判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean isInterrupted() 判断对象关联的线程的标志位是否设置,调用后不清除标志位

等待进程join

我们知道一个线程在完成它的工作后才能进行自己的下一步工作,但在期间想穿插别的工作进来就需要控制线程结束的顺序来实现.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Demo10 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("线程工作中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();

// 让主线程来等待 t 线程执行结束.
// 一旦调用 join, 主线程就会触发阻塞. 此时 t 线程就可以趁机完成后续的工作.
// 一直阻塞到 t 执行完毕了, join 才会解除阻塞, 才能继续执行
System.out.println("join 等待开始");
thread.join();
System.out.println("join 等待结束");
}
}
thread.join工作过程

1.如果线程正在工作中,此时调用thread线程就会阻塞,一直阻塞到thread线程执行结束位置.
2.如果thread线程已经执行结束了,此时调用join线程,就会直接返回,不会涉及阻塞.
【实际开发中比较建议使用join时带有超时时间】

Tips:
sleep本身也有精度误差,调度线程也有开销.(即唤醒线程之后【就绪状态】不是立即回到CPU上运行)

附:

方法 说明
public void join() 等待线程结束
public void join(long millis) 等待线程结束,最多等millis毫秒
public void join(long millis,int nanos) 精度更高但同理

获取线程引用

方法 说明
public static Thread currentThread(); 返回当前线程对象的引用
1
2
3
4
5
6
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}

休眠进程

眼熟吗老师们hh
注意:因为线程是不可控制的,所以这个方法只能保证实际休眠时间大于等于参数设置的休眠时间

方法 说明
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException 更高精度的休眠
1
2
3
4
5
6
7
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3000);
System.out.println(System.currentTimeMillis());
}
}

线程状态

帮助我们快速判断当前程序执行的状况.
线程的状态是一个枚举类型Thread.State.

NEW

安排了工作但还在摸鱼未动
Thread对象已经有了但start方法还没调用.

TERMINATED

工作已完成
Thread对象还在,内核中线程已经结束了.

RUNNABLE

可工作,可以分成正在工作中和即将开始的工作
就绪状态(线程已经在CPU上执行了/线程正在等待CPU执行)

TIMED_WAITING

排队等安排工作
阻塞.sleep这种固定时间的方法产生的阻塞.

WAITING

排队等安排工作
由于wait这种不固定时间的方式产生的阻塞.

BLOCKED

排队等安排工作
阻塞.锁竞争产生.
与阻塞相关的三种状态后续常用于分析“线程卡死”问题

代码套餐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ThreadStateTransfer {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
}
}, "Thread");
//线程未调用start方法对象已创建 此时状态为New
System.out.println(t.getName() + ": " + t.getState());
t.start();
while (t.isAlive()) {
//RUNNABLE 线程正在执行
System.out.println(t.getName() + ": " + t.getState());
}
//线程执行结束TERMINATED
System.out.println(t.getName() + ": " + t.getState());
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ThreadStateTransfer {
public static void main(String[] args) {
final Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("hehe");
}
}
}, "t2");
t2.start();
}
}

image-20240816204151561

image-20240816204208543

使用jconsole观察,t1线程为TIMED_WAITING状态(排队等工作 sleep引起),t2线程为BLOCKED状态(由于t1未执行完 产生锁竞争)

修改sleep为wait后 t2线程结束 打印出“hehe” t1线程为WAITING状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ThreadStateTransfer {
public static void main(String[] args) {
final Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
while (true) {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("hehe");
}
}
}, "t2");
t2.start();
}
}

image-20240816204737122

结论

1.BLOCKED 表示等待获取锁, WAITING 和TIMED_WAITING 表示等待其他线程发来通知. 2.TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒