kim.zhang

风在前,无惧!


  • 首页

  • 标签42

  • 分类12

  • 归档94

  • 搜索

线程的启动与终止.md

发表于 2020-08-10 更新于 2021-11-21 分类于 并发
本文字数: 8.1k 阅读时长 ≈ 7 分钟

start方法源码

  • 对线程状态进行检查,0代表NEW状态
  • 将线程加入线程组
  • 调用native方法start0()
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 synchronized void start() {
// A zero status value corresponds to state "NEW".
if (threadStatus != 0)
throw new IllegalThreadStateException();

/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);

boolean started = false;
try {
// native方法
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {

}
}
}

面试题1:一个线程两次调用start方法会出现什么情况?为什么?

​ 会抛出IllegalThreadStateException。因为调用start方法时会对线程状态进行检查,不是NEW状态就会抛出IllegalThreadStateException。

面试题2:为什么选择调用start方法,而不直接调用run方法?

​ 调用start方法才是真正的启动线程,会有线程的生命周期。而直接调用run方法,只是调用了一个普通方法。

如何正确停止线程:使用interrupt来通知,而不是强制

​ 只是使用interrupt来通知线程,通过设置线程中断标志来实现,线程根据这个标志来自行处理。

interupted的三种情况

  • 线程正常执行完成,调用interrpted方法
  • 线程可能被阻塞
  • 线程在每次迭代后都阻塞

栗子1:线程正常执行,需要线程自行根据中断标志来处理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
26
27
28
29
30
31
32
33
34
35
public class InterruptDemo01 {

public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 999999; i++) {
// 必须自行处理中断,只是调用interrupt方法是无效的
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " interrupted");
break;
}
System.out.println(Thread.currentThread().getName() + i + " is running");
}
});

thread1.start();

// 运行1s后再中断程序
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

thread1.interrupt();

// 等待thread1执行完才输出thread1的线程状态
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("thread1 status:" + thread1.getState());
}
}

执行结果:

1
2
3
4
5
......
Thread-0139724 is running
Thread-0139725 is running
Thread-0 interrupted
thread1 status:TERMINATED

栗子2:线程处在阻塞状态时,interrupt方法结束线程,抛出异常

将以上栗子1中的Thread方法改成以下:

1
2
3
4
5
6
7
8
9
10
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 999999; i++) {
System.out.println(Thread.currentThread().getName() + i + " is running");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});

执行方法:

thread处在sleep状态时,被中断抛出异常。

1
2
3
4
5
6
7
Thread-0999997 is running
Thread-0999998 is running
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadcore.InterruptedDemo01.lambda$main$0(InterruptedDemo01.java:24)
at java.lang.Thread.run(Thread.java:748)
thread1 status:TERMINATED

栗子3:线程在迭代中处于阻塞状态,interrupt方法结束线程,抛出异常

将栗子1中的Thread方法修改为以下:

1
2
3
4
5
6
7
8
9
10
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 999999; i++) {
System.out.println(Thread.currentThread().getName() + i + " is running");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

执行结果:

线程被中断后,抛出异常,catch捕获异常后,线程继续执行。

1
2
3
4
5
6
7
8
Thread-00 is running
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadcore.InterruptedDemo01.run(InterruptedDemo01.java:48)
at java.lang.Thread.run(Thread.java:748)
Thread-01 is running
Thread-02 is running
......

栗子4:捕获异常的位置会影响执行结果

在栗子3的基础上,将捕获异常的代码块范围增大。

1
2
3
4
5
6
7
8
9
10
Thread thread1 = new Thread(() -> {
try {
for (int i = 0; i < 999999; i++) {
System.out.println(Thread.currentThread().getName() + i + " is running");
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});

执行结果:

线程接收到中断通知后,会抛出异常。处理异常后,不再执行循环了,而是执行完线程接下来的任务就结束了。异常的捕获范围影响了线程的执行。

1
2
3
4
5
6
7
8
9
10
11
Thread-00 is running
Thread-01 is running
Thread-02 is running
Thread-03 is running
Thread-04 is running
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadcore.InterruptedDemo01.run(InterruptedDemo01.java:49)
at java.lang.Thread.run(Thread.java:748)
抛出异常后我还会继续执行
thread1 status:TERMINATED

栗子5:在迭代中try..catcha异常会导致interrupt方法失效

在栗子3中,我们在迭代中使用了try..catch..捕获了异常,线程继续往下执行。

那么,是不是加上isInterrupted方法的判断,捕获异常后,线程就不往下执行了呢?

1
2
3
4
5
6
7
8
9
10
11
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 999999 && !Thread.currentThread().isInterrupted(); i++) {
System.out.println(Thread.currentThread().getName() + i + " is running");
try {
// sleep会清除中断标志,导致isInterrupted()方法无法判断
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

执行结果:

线程在捕获了异常后,还是继续往下执行了。

1
2
3
4
5
6
7
8
......
Thread-04 is running
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadcore.InterruptedDemo01.run(InterruptedDemo01.java:48)
at java.lang.Thread.run(Thread.java:748)
Thread-05 is running
......

interrupt()与interrupted()区别

interrupted方法,不管是谁调用,都只对当前线程有效

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 InterruptDemo02 {
public static void main(String[] args) {

Thread t1 = new Thread(() -> {
// 3.条件为!true=false,退出循环
// isInterrupted()返回中断标志true
while (!Thread.currentThread().isInterrupted()) {

}
//4.这里输出的是什么true还是false
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().isInterrupted());
});

//1.开启
t1.start();

//2.中断标记设置为true
t1.interrupt();

try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("main is run over");

}

}

执行结果:

t1.interrupt()设置了中断标志=true,isInterrupted()返回中断标志true,不会清除中断标志

1
2
Thread-0:true
main is run over

如果改成interrupted()方法呢?

1
2
3
4
5
6
7
Thread t1 = new Thread(() -> {
while (!Thread.interrupted()) {

}
//4.这里输出的是什么true还是false
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().isInterrupted());
});

执行结果:

t1.interrupt()设置了中断标志=true,Thread.interrupted()返回中断标志=true,并且清除了中断标志=false

下面的输出语句输出中断标志=false

1
2
Thread-0:false
main is run over

interrupt方法源码

intertupt方法只是设置线程中断标志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void interrupt() {
// interrupt方法只对当前线程有效,谁调用了interrupt方法,就中断谁
if (this != Thread.currentThread())
checkAccess();

synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}

相关方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 不清楚中断标志
public boolean isInterrupted() {
return isInterrupted(false);
}

// 清除当前线程的中断标志
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}

// native方法,参数代表是否清除中断标志
private native boolean isInterrupted(boolean ClearInterrupted);

中断线程的两种姿势

  • 不在线程方法内部try..catch,把异常抛出去,由调用方处理中断异常
  • 在线程方法内部try..catch,并在catch后,再次中断线程,恢复中断,这样方法调用方能处理中断

响应线程中断的N种方法

  • Object :wait
  • Thread:sleep,join
  • BlockQueue:take/put
  • Lock:lockInterruptibly
  • CountDownLatch:await
  • CyclicBarrier:await
  • Exchanger:exchange
  • java.nio.channels.InterruptibleChannel相关方法
  • java.nio.channels.Selector的相关方法

错误的停止线程的方法

  • stop/suspend/resume
  • 用volatile设置的boolean标记位,在阻塞的时候,线程不会响应标记位的更新

栗子1:生产者与消费者模式证明在阻塞的时候,使用volatile设置的标记位来停止线程是错误的

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class ProducerAndConsumer {

public static void main(String[] args) {
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);
Producer producer = new Producer(queue);
Thread thread = new Thread(producer);
thread.start();

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Consumer consumer = new Consumer(queue);
while (consumer.needMoreNums()) {
try {
Object num = queue.take();
System.out.println(num + "被消费了");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者不需要数据了");
producer.cancel = true; // 线程被阻塞,而不会停止
// thread.interrupt(); // 正确的停止方式
}
}

class Consumer {
private BlockingQueue queue;

public Consumer(BlockingQueue queue) {
this.queue = queue;
}

public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}


}

class Producer implements Runnable {

private BlockingQueue queue;

public volatile boolean cancel = false;

public Producer(BlockingQueue queue) {
this.queue = queue;
}

@Override
public void run() {
int num = 0;
try {
// 当消费者不需要数据的时候,生产者阻塞在put这一行代码,导致while的判断条件种cancel无法检测
while (num < 10000 && !cancel) {
if (num % 100 == 0) {
queue.put(num);
System.out.println(num + "被生产出来放入队列了");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
一毛也是爱~
Kim.Zhang 微信支付

微信支付

# 并发基础
线程安全.md
生产者与消费者.md
  • 文章目录
  • 站点概览
Kim.Zhang

Kim.Zhang

且行且珍惜
94 日志
12 分类
42 标签
E-Mail Weibo
  1. 1. start方法源码
  2. 2. 面试题1:一个线程两次调用start方法会出现什么情况?为什么?
  3. 3. 面试题2:为什么选择调用start方法,而不直接调用run方法?
  4. 4. 如何正确停止线程:使用interrupt来通知,而不是强制
  5. 5. interupted的三种情况
    1. 5.1. 栗子1:线程正常执行,需要线程自行根据中断标志来处理interrupt方法的通知
    2. 5.2. 栗子2:线程处在阻塞状态时,interrupt方法结束线程,抛出异常
    3. 5.3. 栗子3:线程在迭代中处于阻塞状态,interrupt方法结束线程,抛出异常
    4. 5.4. 栗子4:捕获异常的位置会影响执行结果
    5. 5.5. 栗子5:在迭代中try..catcha异常会导致interrupt方法失效
  6. 6. interrupt()与interrupted()区别
  7. 7. interrupt方法源码
  8. 8. 中断线程的两种姿势
  9. 9. 响应线程中断的N种方法
  10. 10. 错误的停止线程的方法
    1. 10.1. 栗子1:生产者与消费者模式证明在阻塞的时候,使用volatile设置的标记位来停止线程是错误的
粵ICP备19091267号 © 2019 – 2022 Kim.Zhang | 629k | 9:32
本站总访问量 4 次 | 有 309 人看我的博客啦 |
博客全站共176.7k字
载入天数...载入时分秒...
0%