故事起源于上次阿里電面的3個問題。問題1,jvm中線程分為哪些狀態(tài)。問題2,在執(zhí)行Thread.start()方法后,線程是不是馬上運行。問題3,java中的synchronized和ReentrantLock有什么不同。當時我的回答不是很好,就不說了,面試之后,在網上搜了很多文章,對照著jdk源碼(1.8),發(fā)現這3個問題存在著一些聯系,接下來,就從這3個問題入手,仔細解讀一下線程,synchronized和ReentrantLock。
問題1 jvm中的線程分為哪些狀態(tài)
這個可以看一下jdk中的Thread的State,里面有詳細的注釋,大概含義如下所示
public enum State {
NEW, // 初始化,還沒開始執(zhí)行
RUNNABLE, // 執(zhí)行中
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 超時等待
TERMINATED; // 執(zhí)行完成
}
需要注意的是,這個只是jvm中的線程狀態(tài),并不是操作系統(tǒng)中的線程狀態(tài)。操作系統(tǒng)的線程狀態(tài)中還有一個就緒狀態(tài)(ready)
關于線程狀態(tài)的轉換,盜用了這張圖。來源

線程(英語:thread)是操作系統(tǒng)能夠進行運算調度的最小單位。
問題2 在執(zhí)行Thread.start()方法后,線程是不是馬上運行。
先說答案,不是。
在調用Thread的start方法后,Thread的狀態(tài)變?yōu)?code>RUNNABLE。那么現在這個線程到底有沒有運行呢?查看jdk源碼可知,start方法中調用的是start0的native方法,由他調用底層真正地在操作系統(tǒng)創(chuàng)建一個線程。
操作系統(tǒng)創(chuàng)建的線程馬上就會運行嗎?答案是否定的。線程需要被cpu調度,分配了時間片之后才會真正的運行。因此jvm中的RUNNABLE狀態(tài)其實對應了兩個狀態(tài),ready和runnable。創(chuàng)建的新線程是ready狀態(tài),被cpu調度后成為runnale狀態(tài),這時候才是真正的運行狀態(tài)。
問題3 java中的synchronized和ReentrantLock有什么不同。
這個問題比較龐大,我們先從線程的狀態(tài)入手,來看一下這兩個鎖的區(qū)別。
線程狀態(tài)
首先,我們來思考一下,這兩個鎖都會阻止線程的運行,因此肯定會修改線程的狀態(tài),那么他們阻止之后的線程狀態(tài)是否一致呢?我們帶著這個疑問通過代碼來看一看。
創(chuàng)建一個使用ReentrantLock鎖的線程
public class ThreadStatusTest {
static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread thread1 = new Thread(new Work("thread1"));
Thread thread2 = new Thread(new Work("thread2"));
thread1.start();
thread2.start();
try {
// @1
Thread.sleep(1);
}catch(Exception ex){
}
System.out.println("thread1狀態(tài)" + thread1.getState().toString());
System.out.println("thread2狀態(tài)" + thread2.getState().toString());
}
static class Work implements Runnable{
private String name;
public Work(String name){
this.name = name;
}
@Override
public void run(){
try {
lock.lock();
// @2
Thread.sleep(1 * 1000);
System.out.println(name+"執(zhí)行完成");
}
catch(Exception ex){
}
finally {
lock.unlock();
}
}
}
}
保留@1 @2的sleep,我們測試一下,看看打印的情況,可以看到先執(zhí)行的線程1因為執(zhí)行了內部的sleep后為TIMED_WATTING狀態(tài),后執(zhí)行的線程2為WAITING狀態(tài)。

我們注釋掉@1 @2的sleep,經過多次測試,我們發(fā)現thread2有時會出現BLOCKED和WAITTING狀態(tài)(這里忽略掉RUNNANLE和TERMINATED狀態(tài))。我們帶著疑問繼續(xù)測試synchronized。


和上面方法相同,我們創(chuàng)建一個使用synchronized鎖的線程
public class ThreadStatusTest {
public static void main(String[] args) {
Thread thread1 = new Thread(new SyncWork("thread1"));
Thread thread2 = new Thread(new SyncWork("thread2"));
thread1.start();
thread2.start();
try {
// @1
Thread.sleep(1);
}catch(Exception ex){
}
System.out.println("thread1狀態(tài)" + thread1.getState().toString());
System.out.println("thread2狀態(tài)" + thread2.getState().toString());
}
static class SyncWork implements Runnable{
private String name;
public SyncWork(String name){
this.name = name;
}
@Override
public void run(){
synchronized (SyncWork.class){
try {
// @2
Thread.sleep(1 * 1000);
System.out.println(name+"執(zhí)行完成");
}catch(Exception ex){
}
}
}
}
}
和上面相同,先保留@1 @2的sleep,我們測試一下,看看打印的情況,可以看到線程1因為執(zhí)行了內部的sleep后為TIMED_WATTING狀態(tài),但是和ReentrantLock不同的是,線程2為BLOCKED狀態(tài)。

我們注釋掉@1 @2的sleep,經過多次測試,我們發(fā)現thread2只有BLOCKED狀態(tài)(這里忽略掉RUNNANLE和TERMINATED狀態(tài))。

因此我們在不看源碼的情況下,可以大概得到結論:
- synchronized阻塞的線程狀態(tài)為
BLOCKED - ReentrantLock阻塞的線程狀態(tài)為
BLOCKED或者WAITTING
為什么兩個鎖阻止的線程狀態(tài)是不同的呢?我們仔細看一下Thread.State的注釋,主要是BLOCKED,WAITING,TIMED_WATTING這三個
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
對照著上面線程狀態(tài)轉換的圖就大概了解了。但是其中一些細節(jié),例如什么是monitor,哪里有調用wait(),LockSupport.park()等方法。
這些細節(jié)問題就需要看synchronized對應的字節(jié)碼和jdk中的ReentrantLock源碼。
源碼實現
synchronized
synchronized是java中的關鍵字,它是在jvm層面實現的,所以我們可以查看一下字節(jié)碼看看它是如何實現的。
我們使用javap將上面SyncWork的字節(jié)碼顯示出來,如下所示
public void run();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=1
0: ldc #3 // class design/demo/ThreadStatusTest$SyncWork
2: dup
3: astore_1
4: monitorenter
5: ldc2_w #4 // long 1000l
8: invokestatic #6 // Method java/lang/Thread.sleep:(J)V
11: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
14: new #8 // class java/lang/StringBuilder
17: dup
18: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V
21: aload_0
22: getfield #2 // Field name:Ljava/lang/String;
25: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: ldc #11 // String 執(zhí)行完成
30: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
33: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
36: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
39: goto 43
42: astore_2
43: aload_1
44: monitorexit
45: goto 53
48: astore_3
49: aload_1
50: monitorexit
51: aload_3
52: athrow
53: return
我們仔細觀察生成的字節(jié)碼會發(fā)現synchronized包裹的代碼塊在jvm中生成了monitorenter和monitorenter這兩個命令。若想了解這兩個命令的實現需要一定的c知識,這篇文章講的很詳細,可以看一下。
synchronized關鍵字經過編譯之后,會在同步塊的前后分別形成monitorenter和monitorexit這兩個字節(jié)碼指令。當我們的JVM把字節(jié)碼加載到內存的時候,會對這兩個指令進行解析。這兩個字節(jié)碼都需要一個Object類型的參數來指明要鎖定和解鎖的對象。如果Java程序中的synchronized明確指定了對象參數,那么這個對象就是加鎖和解鎖的對象;如果沒有明確指定,那就根據synchronized修飾的是實例方法還是類方法,獲取對應的對象實例或Class對象來作為鎖對象
說的簡單點就是synchronized會對一個對象的監(jiān)視器(monitor)進行獲取,而這個獲取的過程是排他的,同一時刻只有一個線程能獲取到此監(jiān)視器。
而這個對象是什么呢?這就要看我們是如何使用的synchronized。
- 普通同步方法的synchronized。此對象代表當前的實例對象
- 靜態(tài)同步方法。此對象代表當前的類。即xx.class。
- 同步方法快。此對象代表你指定的對象。
那么這個監(jiān)視器存放在哪里呢?存放在該對象的對象頭中。對象頭中有一個名為MarkWord的部分,該部分記錄了對象和鎖有關的信息。具體的可以看看這個文章。
由于synchronized是在jvm層的實現,因此閱讀它的源碼需要c的知識,這個理解起來還是有些難度的。但是ReentrantLock就不同了,這個鎖是在jdk中的實現,因此可以很方便的查看源碼。
ReentrantLock
還是使用上面的例子,ReentrantLock的使用很簡單,使用new ReentrantLock(boolean isFair)來創(chuàng)建一個公平或者非公平鎖,使用.lock()方法加鎖。使用.unlock()方法,因此我們就從這三個方法入手,來簡單的看一下它是如何實現鎖的。
它的構造方法有兩個,默認是構造一個非公平鎖,這個也是我們經常用的,如下所示
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
無論是NonfairSync還是FairSync他們都是繼承了Sync,并且最終繼承了AbstractQueuedSynchronizer,這就是傳說中的AQS。
AQS中的同步等待隊列提供了線程狀態(tài)管理的基本組件,他提供了一些鉤子函數供子類繼承時擴展。而這個同步隊列主要是由一個帶頭尾指針的雙向鏈表組成,jdk中有詳細的注釋。
static final class Node {
// 等待狀態(tài),主要有3個值。0初始化。-1(SIGNAL)需要喚醒后續(xù)節(jié)點線程。1(CANCELLED)取消等待。
volatile int waitStatus;
volatile Node prev;
volatile Node next;
// 當前節(jié)點對應的線程
volatile Thread thread;
...
private transient volatile Node head;
private transient volatile Node tail;
先大體說一下同步等待隊列的特點:先進先出。獲取鎖失敗的線程構造節(jié)點加入隊列尾部,阻塞自己,等待喚醒。執(zhí)行完成的線程從頭部移出隊列,并喚醒后續(xù)節(jié)點的線程。頭結點是當前獲取到鎖的線程。
還有兩個較為重要的參數
// 位于AbstractOwnableSynchronizer類(AQS的父類),只有獨占鎖使用,代表當前獲取鎖的線程
private transient Thread exclusiveOwnerThread;
// 線程被重入的次數,可能大于0,由于可見性問題使用volatile修飾
private volatile int state;
構造方法先看到這里,我們繼續(xù)解析下一步,.lock()
查看代碼可知真正的lock實現在NonfairSync中,如下所示
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
if代碼塊的上面部分很簡單,使用CAS判斷該鎖有沒有占用,沒有占用的話將state置為1,并修改鎖的當前線程。重點看看acquire(1)方法,這個方法在AQS類中
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
主要是三個方法,tryAcquire(arg)方法是在Sync類中實現的,非公平鎖為nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 當前線程重入
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
可以看到這個方法中有部分代碼是和上面重復的,這也是出于性能考慮,特殊情況早點返回而不再向下執(zhí)行。
若是返回true表示獲取到鎖,若是返回false表示為獲取到鎖,繼續(xù)向下執(zhí)行。接下來先看addWaiter(Node.EXCLUSIVE)方法,這個方法在AQS類中
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); // 創(chuàng)建一個當前線程的節(jié)點
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
// 尾節(jié)點不為空,將該節(jié)點添加到隊尾
node.prev = pred; // 這里先確定次序,再使用原子操作更新尾節(jié)點
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
若隊列為空,將進入enq(node)方法,這個方法在AQS類中,如下所示
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 隊列為空時,原子操作構造頭結點
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 和上層相同代碼,添加到隊尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
這是一個死循環(huán),保證節(jié)點添加到隊列中,和上面類似,包含了和上層方法重復的代碼。注意一下,當前的頭結點是new Node(),里面的線程是空的。
現在節(jié)點已經添加到隊列中了,接下來怎么做,
我們繼續(xù)看下一個方法acquireQueued(final Node node, int arg),這個方法在AQS中,如下所示
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node); // 當前正在執(zhí)行的節(jié)點置為頭結點,這里沒有使用原子操作。
p.next = null; // help GC
failed = false;
return interrupted; // 唯一出口
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) // 這里會阻塞,直到前面的節(jié)點線程執(zhí)行完成
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
看到這里發(fā)現頭結點有點類似哨兵節(jié)點。頭結點為正在執(zhí)行的節(jié)點,頭結點執(zhí)行完成后通知后續(xù)節(jié)點解除阻塞,后續(xù)節(jié)點置為頭結點,開始執(zhí)行。有一種特殊的情況,看一下上面代碼的第二個if代碼段,主要是shouldParkAfterFailedAcquire方法,如下所示
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
Node.SIGNAL的定義如下
waitStatus value to indicate successor's thread needs unparking
即只有當前節(jié)點的狀態(tài)為SIGNAL時才會喚醒后續(xù)節(jié)點,因此節(jié)點若想喚醒,必須保證前驅節(jié)點狀態(tài)為SIGNAL。但是有些節(jié)點可能取消了等待,因此需要從后向前遍歷,直到確定前驅節(jié)點為SIGNAL狀態(tài),然后就可以安心阻塞了。阻塞使用的是LockSupport,一種類似wait,notify的方法。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
執(zhí)行了該方法后,線程就持續(xù)阻塞直到被喚醒。lock()方法到此就結束了,我們接下來看unlock()。
因為已經進行了加鎖,阻塞了其他線程,所以解鎖時相對簡單。
public void unlock() {
sync.release(1);
}
方法很簡單,直接看release方法,該方法在AQS中實現
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
我們先看tryRelease方法,使用的是Sync子類中的實現
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
考慮線程重入的情況,當state為0時,說明當前沒有線程占用鎖,可以進行下一步操作?;氐缴弦粚拥膔elease方法。操作隊列頭結點,通知后繼節(jié)點。
重點看一下通知的方法,即unparkSuccessor
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
// 后繼節(jié)點為空或者為取消狀態(tài),從隊尾向前遍歷,找到相鄰的“正?!惫?jié)點
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 喚醒后繼節(jié)點的線程
LockSupport.unpark(s.thread);
}
解鎖到這里也就結束了??偨Y一下:ReentrantLock主要是用了一個巧妙的數據結構(帶頭尾指針的雙鏈表)和CAS加自旋以及使用LockSupport的park,unpark(類似wait,notify)來實現加鎖和解鎖。
遺留問題
我們上面的測試代碼中,使用ReentrantLock時,被阻塞的線程出現了BLOCKED狀態(tài),但是如果按照ReentrantLock的源碼,出現WAITTING狀態(tài)是正常的,但并不應該出現BLOCKED狀態(tài),若是有讀者明白原因,請在評論中告知,謝謝了。
寫在后面
對照著源碼看,其實流程很清晰。查看源碼的過程中也會發(fā)現,源碼中有很詳細的注釋,我們一定要仔細的看注釋,這樣才能理解的更透徹。
到這里全文就結束了,文中一些內容參考了大佬的這篇文章,大佬寫的比我詳細,但是自己還想結合碰到的問題梳理一遍,自認為能加強理解,若是感覺作者寫的不好可以去看一下原文,相信一定能幫助到您。