前言
看源碼真的是一種享受又恍然大悟的感受,我曾不止一次贊嘆過如Spring、SpringMVC、SpringBoot等源碼設計的優美,我在學習線程、偏向鎖和輕量級鎖等JAVA內置鎖、CAC原理、自旋鎖 公平鎖和讀寫鎖等JUC顯式鎖的時候,總是從源碼中來解答我的疑惑,而JDK關於線程的源碼的設計思想也讓我大加贊嘆。由於筆者最近在看書的時候,有點不上心,特別是在吃完飯后經常看10幾集蠟筆小新(雖然一集只有7分鍾,第五季還加上了片頭曲),為了調整自己的學習狀態我認為分享知識是最好的途徑之一了。
線程的調度和時間片
CPU時間片
CPU的計算頻率非常高,就拿英特爾的的酷睿i9-10900X處理器來說,其基本頻率是3.70GHz,也就是說平均一秒鍾CPU可計算接近40億次,因此我們可以將CPU的時間以毫秒的維度來進行划分,每一個小段就叫做CPU時間片。這里不同的CPU、不同的操作系統其CPU的時間片都不一樣。但是就算以10毫秒來划分CPU時間片,一個CPU時間片的計算次數也高達4億次,其計算量是非常大的。
目前操作系統主流的調度方式是以分配給線程CPU時間片的方式來調度線程,當線程獲取到CPU時間片時,線程便開始運行,如果線程沒有獲取到CPU時間片的話,就依舊處於就緒的狀態等到獲取到時間片后執行,而由於對於CPU時間片我們是以毫秒維度進行划分的,所以對於我們個人來說,其時間是微乎其微的,在這微乎其微的時間內,多個線程快速地切換運行,也就給我們一種多個線程在並發執行的感覺。
線程調度的策略
目前線程調度的策略有兩種,一種是分時調度,另一種是搶占式調度。
什么是分時調度?
所謂的分時調度即是所有線程輪流分配CPU時間片來進行線程的調度。這和大同社會很像,每個線程誰也不多,誰也不少,都讓它撈着,實現CPU時間片分配的線線平等。
什么是搶占式調度?
現代的操作系統一般采取的策略是搶占式調度,所謂的搶占式調度指的是操作系統根據線程的優先級來分配CPU時間片,但不是絕對的,線程優先級高的僅是獲取到CPU時間片的概率要高,與線程優先級低的線程搶占CPU時間片時,不一定是線程優先級高的獲取到CPU時間片。
現如今JAVA的線程調度是委托給操作系統完成的:它會將線程注冊到操作系統的本地線程中,讓操作系統完成線程調度。因此JAVA的線程調度也是搶占式的調度。
線程的優先級
對於搶占式調度策略來說,線程的優先級很重要,Thread類提供了一個實例屬性和兩個實例方法(看上去是這樣的)來操作線程的優先級。
我們進入Thread類來看一下吧:
-
實例屬性: priority (優先級,為int類型,范圍為1~10,越大優先級越高。)
-- 源碼定義:
private int priority;
-
獲取線程優先級實例方法源碼:
public final int getPriority() { return priority; }
-
設置線程優先級實例方法源碼:
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
我們解讀一下設置線程優先級的源碼:
- 首先它會檢查一下你有沒有這個權限去設置,點進去checkAccess()會發現它會獲取你的安全管理器文件進而來判斷你有沒有權限,詳情看: https://www.cnblogs.com/yaowen/p/10117893.html
- 然后它會判斷你所給的優先級是不是在1至10之間的,若不是便會拋出一個IllegalArgumentException異常。
- 接着它會獲取線程組,所謂的線程組其實是由線程組成的管理線程的類,如果設置線程的優先級要大於線程組的優先級的話,就將重置設置線程的優先級,使其等於線程組的優先級,最后調用setPriority0去真正的設置線程的優先級,也就是說setPriority方法設置線程優先級其實內部是調用了setPriority0方法,這里我們可以看到非業務邏輯和業務方法的分離,很美妙的一種寫法。
接着我們寫一個例子來證明是不是優先級高的獲取CPU時間片的機率大
/**
* 線程的優先級
*/
public class PriorityDemo {
public static final int SLEEP_GAP = 1000;
static class PrioritySetThread extends Thread{
static AtomicInteger threadNo = new AtomicInteger(1);
public PrioritySetThread() {
super("thread-"+threadNo.get());
threadNo.incrementAndGet();
}
@Override
public synchronized void start() {
super.start();
}
public volatile long amount = 0;
@Override
public void run() {
for (int i = 0;; i++) {
this.amount++;
}
}
}
public static void main(String[] args) throws InterruptedException {
PrioritySetThread[] threads = new PrioritySetThread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new PrioritySetThread();
threads[i].setPriority(i+1);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
//睡眠一秒
ThreadUtil.sleepMilliSeconds(SLEEP_GAP);
for (int i = 0; i < threads.length; i++) {
threads[i].stop();//停止線程
}
for (int i = 0; i < threads.length; i++) {
System.out.println(threads[i].getName()+"-優先級為-:"+threads[i].getPriority()+"-機會值為-"+threads[i].amount);}
}
}
結果為:
從結果我們可以看出,線程優先級大的線程較於線程優先級小的線程獲取CPU時間片越多,執行得到的值越大。但優先級大的如thread-8執行的值要比thread-7的值要小,這也意味着線程優先級高的僅是獲取到CPU時間片的概率要高,與線程優先級低的線程搶占CPU時間片時,不一定是線程優先級高的獲取到CPU時間片。
線程的生命周期
線程的生命周期有6種,也分別代表着線程的不同的狀態。Thread類提供了一個實例屬性和實例方法可以來獲取線程當前的狀態,分別是:
-
threadStatus(實例屬性),定義是
private volatile int threadStatus;
-
獲取線程狀態的實例方法:
public State getState() { // get current thread state return jdk.internal.misc.VM.toThreadState(threadStatus); }
jdk.internal.misc.VM 類一般是直接操控操作系統,不受JVM管制。如其提供的Unsafe類。
狀態有六種,從枚舉類Status我們可以得知:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* 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 {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* 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,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
-
NEW 表示是新建線程,此時並沒有使用thread.start()啟動線程去運行用戶代碼邏輯塊。
-
RUNNABLE 是JAVA定義的,它在操作系統其實有兩種意思,一種是線程處於就緒狀態,隨時等着操作系統分配CPU時間片,另一種則是操作系統已分配CPU時間片,該線程處於執行狀態。
-
BLOACKED 是阻塞的意思,當線程正在執行,執行到獲取鎖的代碼的時候,發現鎖已經被其他線程占有,此時線程就會進入阻塞狀態,阻塞狀態期間操作系統不分配CPU時間片,當其他線程釋放鎖時,會喚醒該線程,狀態變為Runnable(就緒),然后去爭搶鎖。
-
TIMED_WAITING 表示當前的線程處於限時等待狀態,在等待狀態時,操作系統也不會給該線程分配CPU時間片。一般進入該狀態都是調用了線程的sleep(int n)、wait(time)、join(time)等方法
-
WAITING 與TIME_WAITING類似,只不過不是限時等待,是一直處於等待狀態中,一般進入這種狀態是調用了sleep()、wait()、join()等不限時的方法。
-
TERMINATED 當線程執行完任務或者線程在執行任務中途發生異常使得線程關閉時,線程的狀態為轉為此狀態。
JVM自帶jstack工具使用
Jstack工具是JAVA虛擬機自帶的一種堆棧跟蹤工具。Jstack用於生成或導出JVM虛擬機運行實例當前時刻的線程快照。有了這個工具,我們可以定位線程出現阻塞,停頓,和長時間運行的原因。下面我簡單的演示一下jstack工具的使用。