【Java并發(fā)學(xué)習(xí)】之詳解線程的點(diǎn)滴(1)
前言
在前面的幾個小節(jié)中,我們粗略地學(xué)習(xí)了線程的基本概念,定義任務(wù)的方式,并且通過Java中的多線程API來驅(qū)動對應(yīng)的任務(wù),然后學(xué)習(xí)了線程的互斥與同步關(guān)系,線程之間進(jìn)行資源共享后的同步操作等等一些簡單的內(nèi)容,可以說,線程中的主要內(nèi)容我們已經(jīng)掌握了,然而,也僅僅只是簡單內(nèi)容,很多比較細(xì)節(jié)、復(fù)雜的東西在之前我們刻意避開了,接下來的幾個小節(jié)中,我們就來具體地學(xué)習(xí)線程的各個細(xì)節(jié)內(nèi)容
線程的屬性
線程有許多的屬性,比如線程的ID、名字、優(yōu)先級、狀態(tài)等,接下來我們就來具體學(xué)習(xí)這些屬性
線程的ID
每個線程都有對應(yīng)的獨(dú)一無二的ID,這些ID是由JVM在運(yùn)行的時(shí)候?yàn)榫€程設(shè)置的,用于區(qū)分不同的線程,并且不對外提供設(shè)置線程ID的接口,也就是說,在Java中,我們僅能夠獲取多線程的ID,而不能設(shè)置線程的ID,訪問代碼如下
class DetailTask implements Runnable{
@Override
public void run() {
System.out.println("Details : " + Thread.currentThread().getId());
// 其中Thread.currentThread()用于獲取當(dāng)前執(zhí)行的線程
// 通過thread.getId()則可以獲取對應(yīng)的線程的ID
}
}
線程的名字
在線程比較多的情況下,我們會傾向于根據(jù)執(zhí)行不同任務(wù)的不同線程來為其設(shè)置名字,方便在日志以及其他場景中使用到,設(shè)置以及獲取線程的名字操作具體如下
class DetailTask implements Runnable{
private static int cnt = 0;
// 注意這里的代碼是非線程安全的
@Override
public void run() {
Thread.currentThread().setName("Thread " + cnt++); // 設(shè)置線程名字
System.out.println("Details : " + Thread.currentThread().getName()); // 獲取線程的名字
}
}
線程優(yōu)先級
優(yōu)先級是一個比較有價(jià)值的概念,在Java中,線程之間的調(diào)度采用的是基于時(shí)間片的輪轉(zhuǎn)調(diào)度方式,其中在線程進(jìn)行切換,重新調(diào)度時(shí),調(diào)度的主要依據(jù)就是優(yōu)先級,通常來說,優(yōu)先級比較高的線程,被調(diào)度的可能性會比較大,也就是說,在單位時(shí)間內(nèi),優(yōu)先級高的線程被調(diào)度的次數(shù)會大于優(yōu)先級低的線程
不過,在設(shè)置線程的優(yōu)先級的時(shí)候,需要注意一下幾點(diǎn)
- 線程的有效優(yōu)先級僅能為 0 - 10 之間的整數(shù),其他數(shù)字則會拋出
java.lang.IllegalArgumentException - 由于不同的操作系統(tǒng)的優(yōu)先級的范圍不一致,而映射到0-10之間則會出現(xiàn)一些意想不到的驚喜,所以一般在設(shè)置優(yōu)先級的時(shí)候,不能過分依賴于優(yōu)先級,盡量只使用
Thread.MAX_PRIORITY、Thread.MIN_PRIORITY以及默認(rèn)的優(yōu)先級,分別對應(yīng)的數(shù)值為10,0,1 - 設(shè)置優(yōu)先級僅僅僅能提高線程被調(diào)度的概率,也就是僅能提高線程被調(diào)度的次數(shù),但是并不能保證執(zhí)行次數(shù)一定會高,所以,不能僅依賴優(yōu)先級來實(shí)現(xiàn)如同步、合作之類的操作
- 優(yōu)先級的設(shè)置必須在線程啟動之前設(shè)置,也就是必須在thread.start()方法執(zhí)行之前進(jìn)行設(shè)置,否則,會采用默認(rèn)的優(yōu)先級,言下之意即不能在run方法中進(jìn)行優(yōu)先級的設(shè)置
for (int i = 0; i < 10; i++){
Thread thread = new Thread(task);
System.out.println("Priority " + thread.getPriority());
if (i % 2 == 0){
thread.setPriority(Thread.MIN_PRIORITY); // 設(shè)置優(yōu)先級為最低
}else {
thread.setPriority(Thread.MAX_PRIORITY); // 設(shè)置優(yōu)先級為最高
}
thread.start();
}
線程的狀態(tài)
通常來講,線程的數(shù)量是多于CPU的數(shù)量,這也就意味著,為了保證每個線程都能得到執(zhí)行,在特定時(shí)刻,總會有線程處于非運(yùn)行狀態(tài),當(dāng)然,Java中線程的狀態(tài)不止這兩種,具體如下
-
NEW:新建狀態(tài),此時(shí)線程已經(jīng)被賦予了任務(wù),但需要注意的是,此時(shí)執(zhí)行該線程所需要的資源仍未準(zhǔn)備好,是不具備執(zhí)行條件的,也就是在調(diào)用start()方法之前的狀態(tài) -
RUNNABLE:可運(yùn)行狀態(tài),表明此時(shí)該線程已經(jīng)是處于可執(zhí)行的狀態(tài),這里需要注意的是,可運(yùn)行狀態(tài)可以是該線程正在執(zhí)行,也可能該線程的除了CPU之外的其他資源都已經(jīng)準(zhǔn)備完畢,只要分配給其CPU就能執(zhí)行。注意是Runnable而不是Run -
BLOCKED:阻塞狀態(tài),線程如果處于該狀態(tài),則表明此時(shí)該線程正在等在某個鎖的釋放 -
WAITING:等待狀態(tài),表明此時(shí)該線程正在等待某些其他線程的資源,通常是調(diào)用了Object.wait()、join()、LockSupport.park()這幾個方法 -
TIMED_WAITING:等待超時(shí)狀態(tài),同樣是等待狀態(tài),只是多了個計(jì)時(shí)器,通常是調(diào)用了上面幾個方法中帶時(shí)間參數(shù)的重載方法 -
TERMINATED:中斷/結(jié)束狀態(tài),表明此時(shí)該線程已經(jīng)處于執(zhí)行完畢的狀態(tài)或者是被其他線程或者JVM中斷,結(jié)束其運(yùn)行
關(guān)于這幾個狀態(tài)的詳細(xì)介紹,可以參考JDK源碼中的注釋
/* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
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 <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,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
守護(hù)線程
上面我們主要學(xué)習(xí)了線程的幾個屬性,ID、名字、優(yōu)先級、狀態(tài)等,接下來我們來學(xué)習(xí)一個新的概念,守護(hù)線程
守護(hù)線程是一類特殊的線程,主要的特點(diǎn)如下
- 被調(diào)度的頻率較低
- 當(dāng)當(dāng)前系統(tǒng)中的所有非守護(hù)線程結(jié)束時(shí),守護(hù)線程也會結(jié)束,而且不管正在執(zhí)行什么任務(wù)
基于守護(hù)線程的特點(diǎn),在使用的時(shí)候需要注意,不能在守護(hù)線程中做一些邏輯比較復(fù)雜的操作,不能做一些比較重要的操作
一個典型的使用守護(hù)線程的例子就是JVM中的垃圾回收器
public static void main(String[] args) {
Thread daemonTask = new Thread(new DaemonTask());
daemonTask.setDaemon(true);// 設(shè)置為守護(hù)線程,這里同樣需要注意,必須在線程啟動之前進(jìn)行設(shè)置
daemonTask.start();
}
class DaemonTask implements Runnable{
@Override
public void run() {
while (true){
System.out.println("I'm Daemon thread");
}
}
}
總結(jié)
本小計(jì)我們主要學(xué)習(xí)了線程的相關(guān)屬性,包括了ID、名字、優(yōu)先級、狀態(tài),以及守護(hù)線程的概念和設(shè)置守護(hù)線程的方法,線程中的異常處理,接下來我們將學(xué)習(xí)線程的中斷以及線程的異常處理等等的內(nèi)容