线程状态以及转化

NEW(新建)

  • 使用new创建出线程后,进入新建状态
  • 此时jvm为其分配内存以及其成员变量
  • 除此之外没有任何特征,方法也不会被执行

RUNNABLE(就绪)

  • 调用对象的start()方法,进入就绪状态
  • 此时jvm会为其创建方法调用栈和程序计数器
  • 线程拥有被CPU调度资格,开始疯狂争夺使用权

RUNNING(运行)

  • 抢到CPU使用权时,开始执行run()方法,进入运行状态
  • 线程只有通过start()后争夺到CPU时间片的方式运行run()方法,才可以实现异步执行
  • 如果直接调用run()方法运行,系统会当作普通方法,不会异步执行

BLOCKED(阻塞)

  • 处于运行状态的线程在进入synchronized关键字修饰的方法或代码块时,进入阻塞状态
  • 阻塞的过程就是线程在抢夺锁的过程,因此阻塞是被动的
  • 阻塞在某个锁上的线程,在锁被释放后会主动去争取,争取到锁后回到运行状态,因此脱离阻塞状态是主动的

WAITING(等待)

  • 调用wait()、join()方法时,进入等待状态
  • 因此进入等待状态是主动的,需要有事件主动唤醒

TIMED_WAITING(等待)

  • 调用sleep(long)、wait(long)、join(long)方法时,进入超时等待状态
  • 同等待状态,到达参数指定时间自动唤醒

TERMINATED(终止)

  • run()方法或call()方法运行完毕,线程正常结束
  • 线程执行代码过程中抛出未捕获异常或直接ERROR
  • 调用stop()方法,也是个奇葩的方法,不推荐使用

附加状态转化图:

类型转化。

isAlive()

1
public final native boolean isAlive();

判断当前线程是否活着,只有当线程进入RUNNABLE(就绪)或RUNNING(运行)状态才返回true。

sleep(long millis)

1
public static native void sleep(long millis) throws InterruptedException;

Thread的静态方法,使当前线程放弃CPU时间片,在指定时间内不参与CPU竞争,在到达指定时间后变为runnable状态并重新加入CPU竞争。如果当前线程持有锁,在睡眠过程中不会放弃锁的占有权

join(long millis)

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

// 无参方法,调用重载方法传入固定参数0
public final void join() throws InterruptedException {
join(0);
}

// 支持超时的join方法
public final synchronized void join(long millis) throws InterruptedException {

// 获取当前时间戳
long base = System.currentTimeMillis();
// 记录已经延迟多久
long now = 0;

// 参数校验,不能小于0
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

// 没有超时限制情况下
if (millis == 0) {

// 线程只有处于活着状态才进行处理
while (isAlive()) {

// 这里意味着调用此方法的线程直接被wait方法挂起,没有提供任何notify方法唤醒,只能被动的等待线程运行完毕后死亡
wait(0);
}
} else {

// 线程只有处于活着状态才进行处理
while (isAlive()) {

// 还需要延迟多久
long delay = millis - now;

// 如果已达到延迟时间限制,跳出循环
if (delay <= 0) {
break;
}

// 挂起进入等待状态
wait(delay);

// 执行到这里说明等待时间已到,重新计算已经延迟多久了,等待下一次进入while循环调用break
now = System.currentTimeMillis() - base;
}
}
}

源码可以看出来join是用的wait()实现的,wait方法是object的方法,作用是让调用这个Object.wait()的线程处于等待状态,除非其他线程调用这个Object.notify()唤醒,或者这个Object死亡阻塞状态才会变成可运行状态,如果join方法带参数,那就等到参数时间结束自动唤醒自己。

如果在一个线程执行中创建另外一个线程并使用join(),那么主线程会被挂起,等待子线程执行完在继续往下执行。说白了和执行过程中调用另一个方法没什么区别,无非就是有个超时时间限制,超过时间限制主线程就取消等待继续执行。使用isAlive()进行判断,也就意味着线程如果没有进入RUNNABLE(就绪)或RUNNING(运行)状态,join方法不会起任何作用。

join其实合理理解成是线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,但是如果调用线程的join方法其实已经失去了并行的意义,虽然存在多个线程,但是本质上还是串行的,最后join的实现其实是基于等待通知机制(wait+notify)的。

yield()

1
public static native void yield();

Thread的静态方法,暂停当前正在执行的线程对象,并执行其他线程。被暂停的线程会让出CPU的使用权给其他线程获得运行机会,自身转化为RUNNABLE(就绪)状态,但是这么做并不一定能达到让出CPU资源的目的,因为让出CPU使用权的时候,自身回到可运行状态与其他同优先级线程一起再去竞争CPU时间片,如果这个线程是个欧皇还会被再次选中,出现这种情况也就意味着此次yield()方法并没有任何效果。

目前想不到什么应用场景,如果一个线程的优先级特别低,执行内容也不是很重要,又怕他被CPU调度的次数多,可以适当的调用此方法减少执行的次数,把CPU资源给其他重要的线程工作。

interrupt()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void interrupt() {

//如果调用中断的是线程自身,则不需要进行安全性判断
if (this != Thread.currentThread())
checkAccess();

//
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 只是设置中断标志
b.interrupt(this);
return;
}
}
interrupt0();
}

每个线程内部都维护了一个中断标志(默认false),调用线程的interrupt()方法时会根据当前线程的中断标志和阻塞情况,判断是否需要抛出异常:

  • 如果中断标志为false,且没有被阻塞,修改中断标志为true。
  • 如果中断标志为true,此时调用wait、sleep、join方法时会抛出InterruptedException异常,恢复中断标志为false。
  • 如果已经被wait、sleep、join方法阻塞,调用interrupt()会抛出InterruptedException异常,恢复中断标志为false。

这里提到的阻塞,只是因为wait、sleep、join方法导致线程被堵住无法继续执行,并不是线程七大状态的BLOCKED(阻塞)状态。BLOCKED(阻塞)状态只由synchronized导致,而且不能被打断,相同的,IO阻塞也不能被打断。

由此可以看出来interrupt()方法中断的不是线程的运行,而是中断线程的阻塞状态,并且采用抛异常的方式引起线程的注意,被中断线程可以通过try catch方式自己决定如何应对中断信号。

比如使用kafka采用while(true)的方式消费数据时,又希望在某个时刻终止这个线程,并且终止过程中要保证此刻正在处理的那条消息处理完毕后才能终止,可以采用interrupt()方法+wait、sleep、join的一种来实现:

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
public void run(){

// 创建消费者
Properties props = createProperties("localhost:9092", "groups");
props.put("enable.auto.commit", "false");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

// 设置消费topic
consumer.subscribe(Arrays.asList("topic-name"));

while (true) {

// 每次拉取消息
ConsumerRecords<String, String> records = consumer.poll(100);

// 循环处理
for (ConsumerRecord<String, String> record : records) {

// 消费逻辑..

// wait或sleep或join阻塞1毫秒,试探线程有没有被中断
try {
Thread.wait(1);
} catch (InterruptedException e) {
// 关闭消费者对象
consumer.close();
return;
}
}

}
}

stop()

强制终止线程的运行,并立即释放掉此线程持有的锁,这些锁可能用来维护数据一致性的,所以此方法被废弃。

评论