JDK源码分析——Thread类

线程状态

线程状态解释

源码状态枚举类:

1
2
3
4
5
6
7
8
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
  • NEW:线程刚创建, 尚未启动;
  • RUNNABLE:线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等;
  • BLOCKED:在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized块的执行释放, 或者可重入的synchronized块里别人调用wait()方法, 也就是这里是线程在等待进入临界区;
  • WAITING:指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用notify/notifyAll一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify, 线程调用了join方法join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束;
  • TIMED_WAITING:有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态;
  • TERMINATED:该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)。

线程状态转化图

线程状态转换图

阻塞和等待的区别

阻塞: 当一个线程试图获取对象锁(非java.util.concurrent库中的锁,即synchronized),而该锁被其他线程持有,则该线程进入阻塞状态。它的特点是使用简单,由JVM调度器来决定唤醒自己,而不需要由另一个线程来显式唤醒自己,不响应中断。
等待: 当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显式地唤醒自己,实现灵活,语义更丰富,可响应中断。例如调用:Object.wait()、Thread.join()以及等待Lock或Condition。

注意: 虽然synchronized和JUC里的Lock都实现锁的功能,但线程进入的状态是不一样的。synchronized会让线程进入阻塞态,而JUC里的Lock是用LockSupport.park()/unpark()来实现阻塞/唤醒的,会让线程进入等待态。但话又说回来,虽然等锁时进入的状态不一样,但被唤醒后又都进入runnable态,从行为效果来看又是一样的。

基本属性

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
 private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;

/* Whether or not to single_step this thread. */
private boolean single_step;

/* Whether or not the thread is a daemon thread. */
private boolean daemon = false;

/* JVM state */
private boolean stillborn = false;

/* What will be run. */
private Runnable target;

/* The group of this thread */
private ThreadGroup group;

/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;

/* The inherited AccessControlContext of this thread */
private AccessControlContext inheritedAccessControlContext;

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

/*
* The requested stack size for this thread, or 0 if the creator did
* not specify a stack size. It is up to the VM to do whatever it
* likes with this number; some VMs will ignore it.
*/
private long stackSize;

/*
* JVM-private state that persists after native thread termination.
*/
private long nativeParkEventPointer;

/*
* Thread ID
*/
private long tid;

/* For generating thread ID */
private static long threadSeqNumber;

/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
*/

private volatile int threadStatus = 0;


private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}

/**
* The argument supplied to the current call to
* java.util.concurrent.locks.LockSupport.park.
* Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
* Accessed using java.util.concurrent.locks.LockSupport.getBlocker
*/
volatile Object parkBlocker;

/* The object in which this thread is blocked in an interruptible I/O
* operation, if any. The blocker's interrupt method should be invoked
* after setting this thread's interrupt status.
*/
private volatile Interruptible blocker;
private final Object blockerLock = new Object();

/* Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code
*/
void blockedOn(Interruptible b) {
synchronized (blockerLock) {
blocker = b;
}
}

/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;

/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;

/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
  • name:表示Thread的名字,可以通过Thread类的构造器中的参数来指定线程名字;
  • priority:表示线程的优先级(最大值为10,最小值为1,默认值为5);
  • daemon:表示线程是否是守护线程,如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。在JVM中,垃圾收集器线程就是守护线程;
  • target:表示要执行的任;
  • group:线程群组。

主要方法

start()

新启一个线程执行其run()方法,一个线程只能start一次。主要是通过调用native start0()来实现。

run()

run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当该线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

sleep()

sleep方法有两个重载版本:

1
2
3
sleep(long millis)     //参数为毫秒

sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒

sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。

但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

yield()

调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

join()

join方法有三个重载版本:

1
2
3
join()
join(long millis) //参数为毫秒
join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒

join()实际是利用了wait(),只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:1)等待时间到;2)目标线程已经run完(通过isAlive()来判断)。

interrupt()

此操作会中断等待中的线程,并将线程的中断标志位置位。如果线程在运行态则不会受此影响。

可以通过以下三种方式来判断中断:

  1. isInterrupted()

    此方法只会读取线程的中断标志位,并不会重置。

  2. interrupted()

    此方法读取线程的中断标志位,并会重置。

  3. throw InterruptException

    抛出该异常的同时,会重置中断标志位。

方法 解释
public static boolean interrupted() 测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
public boolean isInterrupted() 测试线程是否已经中断。线程的中断状态不受该方法的影响。
public void interrupt() 中断线程。

suspend()/resume()

挂起线程,直到被resume,才会苏醒。

但调用suspend()的线程和调用resume()的线程,可能会因为争锁的问题而发生死锁,所以JDK 7开始已经不推荐使用了。