
1. 執行緒的創建
首先我們來復習我們學習 java 時接觸的執行緒創建,這也是面試的時候喜歡問的,有人說兩種也有人說三種四種等等,其實我們不能去死記硬背,而應該深入理解其中的原理,當我們理解后就會發現所謂的創建執行緒實質都是一樣的,在我們面試的程序中如果我們能從本質出發回答這樣的問題,那么相信一定是個加分項!好了我們不多說了,開始今天的 code 之路
1.1 **繼承 Thread 類創建執行緒 **
**
- 這是我們最常見的創建執行緒的方式,通過繼承
Thread類來重寫run方法,
代碼如下:
/**
* 執行緒類
* url: www.i-code.online
* @author: anonyStar
* @time: 2020/9/24 18:55
*/
public class ThreadDemo extends Thread {
@Override
public void run() {
//執行緒執行內容
while (true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThredDemo 執行緒正在執行,執行緒名:"+ Thread.currentThread().getName());
}
}
}
測驗方法:
@Test
public void thread01(){
Thread thread = new ThreadDemo();
thread.setName("執行緒-1 ");
thread.start();
while (true){
System.out.println("這是main主執行緒:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果:
繼承
Thread的執行緒創建簡單,啟動時直接呼叫start方法,而不是直接呼叫run方法,直接呼叫run等于呼叫普通方法,并不是啟動執行緒
1.2 **實作 Runnable 介面創建執行緒 **
**
- 上述方式我們是通過繼承來實作的,那么在
java中提供了Runnable介面,我們可以直接實作該介面,實作其中的run方法,這種方式可擴展性更高
代碼如下:
/**
* url: www.i-code.online
* @author: anonyStar
* @time: 2020/9/24 18:55
*/
public class RunnableDemo implements Runnable {
@Override
public void run() {
//執行緒執行內容
while (true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("RunnableDemo 執行緒正在執行,執行緒名:"+ Thread.currentThread().getName());
}
}
}
測驗代碼:
@Test
public void runnableTest(){
// 本質還是 Thread ,這里直接 new Thread 類,傳入 Runnable 實作類
Thread thread = new Thread(new RunnableDemo(),"runnable子執行緒 - 1");
//啟動執行緒
thread.start();
while (true){
System.out.println("這是main主執行緒:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運行結果:
1.3 實作 Callable 介面創建執行緒
- 這種方式是通過 實作
Callable介面,實作其中的call方法來實作執行緒,但是這種執行緒創建的方式是依賴于 ****FutureTask **包裝器**來創建Thread, 具體來看代碼
代碼如下:
/**
* url: www.i-code.online
* @author: anonyStar
* @time: 2020/9/24 18:55
*/
public class CallableDemo implements Callable<String> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
@Override
public String call() throws Exception {
//執行緒執行內容
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("CallableDemo 執行緒正在執行,執行緒名:"+ Thread.currentThread().getName());
return "CallableDemo 執行結束,,,,";
}
}
測驗代碼:
@Test
public void callable() throws ExecutionException, InterruptedException {
//創建執行緒池
ExecutorService service = Executors.newFixedThreadPool(1);
//傳入Callable實作同時啟動執行緒
Future submit = service.submit(new CallableDemo());
//獲取執行緒內容的回傳值,便于后續邏輯
System.out.println(submit.get());
//關閉執行緒池
service.shutdown();
//主執行緒
System.out.println("這是main主執行緒:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
結果:
有的時候,我們可能需要讓一步執行的執行緒在執行完成以后,提供一個回傳值給到當前的主執行緒,主執行緒需要依賴這個值進行后續的邏輯處理,那么這個時候,就需要用到帶回傳值的執行緒了
關于執行緒基礎知識的如果有什么問題的可以在網上查找資料學習學習!這里不再闡述
2. 執行緒的生命周期
- Java 執行緒既然能夠創建,那么也勢必會被銷毀,所以執行緒是存在生命周期的,那么我們接下來從執行緒的生命周期開始去了解執行緒,
2.1 執行緒的狀態
2.1.1 執行緒六狀態認識
執行緒一共有 6 種狀態(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)
-
NEW:初始狀態,執行緒被構建,但是還沒有呼叫 start 方法
-
RUNNABLED:運行狀態,JAVA 執行緒把作業系統中的就緒和運行兩種狀態統一稱為“運行中”
-
BLOCKED:阻塞狀態,表示執行緒進入等待狀態, 也就是執行緒因為某種原因放棄了 CPU 使用權,阻塞也分為幾種情況
- 等待阻塞:運行的執行緒執行 wait 方法,jvm 會把當前執行緒放入到等待佇列? 同步阻塞:運行的執行緒在獲取物件的同步鎖時,若該同步鎖被其他執行緒鎖占用了,那么 jvm 會把當前的執行緒放入到鎖池中
- 其他阻塞:運行的執行緒執行 Thread.sleep 或者 t.join 方法,或者發出了 I/O 請求時,JVM 會把當前執行緒設定為阻塞狀態,當 sleep 結束、join 執行緒終止、io 處理完畢則執行緒恢復
-
TIME_WAITING:超時等待狀態,超時以后自動回傳
-
TERMINATED:終止狀態,表示當前執行緒執行完畢

2.1.2 代碼實操演示
- 代碼:
public static void main(String[] args) {
////TIME_WAITING 通過 sleep wait(time) 來進入等待超時中
new Thread(() -> {
while (true){
//執行緒執行內容
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Time_Waiting").start();
//WAITING, 執行緒在 ThreadStatus 類鎖上通過 wait 進行等待
new Thread(() -> {
while (true){
synchronized (ThreadStatus.class){
try {
ThreadStatus.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"Thread_Waiting").start();
//synchronized 獲得鎖,則另一個進入阻塞狀態 blocked
new Thread(() -> {
while (true){
synchronized(Object.class){
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"Object_blocked_1").start();
new Thread(() -> {
while (true){
synchronized(Object.class){
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"Object_blocked_2").start();
}
啟動一個執行緒前,最好為這個執行緒設定執行緒名稱,因為這樣在使用 jstack 分析程式或者進行問題排查時,就會給開發人員提供一些提示
2.1.3 執行緒的狀態堆疊
? 運行該示例,打開終端或者命令提示符,鍵入“ jps ”, ( JDK1.5 提供的一個顯示當前所有 java 行程 pid 的命令)
? 根據上一步驟獲得的 pid ,繼續輸入 jstack pid (jstack是 java 虛擬機自帶的一種堆疊跟蹤工具,jstack 用于列印出給定的 java 行程 ID 或 core file 或遠程除錯服務的 Java 堆疊資訊)
3. 執行緒的深入決議
3.1 執行緒的啟動原理
- 前面我們通過一些案例演示了執行緒的啟動,也就是呼叫
start()方法去啟動一個執行緒,當run方法中的代碼執行完畢以后,執行緒的生命周期也將終止,呼叫start方法的語意是當前執行緒告訴JVM,啟動呼叫start方法的執行緒, - 我們開始學習執行緒時很大的疑惑就是 啟動一個執行緒是使用
start方法,而不是直接呼叫run方法,這里我們首先簡單看一下start方法的定義,在Thread類中
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
//執行緒呼叫的核心方法,這是一個本地方法,native
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//執行緒呼叫的 native 方法
private native void start0();
- 這里我們能看到
start方法中呼叫了native方法start0來啟動執行緒,這個方法是在Thread類中的靜態代碼塊中注冊的 , 這里直接呼叫了一個native方法registerNatives
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
-
由于
registerNatives方法是本地方法,我們要看其實作原始碼則必須去下載jdk原始碼,關于jdk及虛擬機hotspot的原始碼下載可以去openJDK官網下載 ,參考: -
我們可以本地查看原始碼或者直接去 http://hg.openjdk.java.net/jdk8u/jdk8u60/jdk/file/935758609767/src/share/native/java/lang/Thread.c 查看
Thread類對應的本地方法.c檔案,

- 如上圖,我們本地下載
jdk工程,找到src->share->native->java->lang->Thread.c檔案

- 上面是
Thread.c中所有代碼,我們可以看到呼叫了RegisterNatives同時可以看到method集合中的映射,在呼叫本地方法start0時,實際呼叫了JVM_StartThread,它自身是由c/c++實作的,這里需要在 虛擬機原始碼中去查看,我們使用的都是hostpot虛擬機,這個可以去openJDK官網下載,上述介紹了不再多說 - 我們看到
JVM_StartThread的定義是在jvm.h原始碼中,而jvm.h的實作則在虛擬機hotspot中,我們打開hotspot原始碼,找到src -> share -> vm -> prims ->jvm.cpp檔案,在2955行,可以直接檢索JVM_StartThread, 方法代碼如下:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread = NULL;
bool throw_illegal_thread_state = false;
{
MutexLocker mu(Threads_lock);
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
throw_illegal_thread_state = true;
} else {
// We could also check the stillborn flag to see if this thread was already stopped, but
// for historical reasons we let the thread detect that itself when it starts running
// <1> :獲取當前行程中執行緒的數量
jlong size =
java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
size_t sz = size > 0 ? (size_t) size : 0;
// <2> :真正呼叫創建執行緒的方法
native_thread = new JavaThread(&thread_entry, sz);
if (native_thread->osthread() != NULL) {
// Note: the current thread is not being used within "prepare".
native_thread->prepare(jthread);
}
}
}
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
assert(native_thread != NULL, "Starting null thread?");
if (native_thread->osthread() == NULL) {
// No one should hold a reference to the 'native_thread'.
delete native_thread;
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
"unable to create new native thread");
}
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
"unable to create new native thread");
}
// <3> 啟動執行緒
Thread::start(native_thread);
JVM_END
JVM_ENTRY是用來定義JVM_StartThread函式的,在這個函式里面創建了一個真正和平臺有關的本地執行緒, 上述標記 <2> 處
- 為了進一步執行緒創建,我們在進入
new JavaThread(&thread_entry, sz)中查看一下具體實作程序,在thread.cpp檔案1566行處定義了new的方法

- 對于上述代碼我們可以看到最終呼叫了
os::create_thread(this, thr_type, stack_sz);來實作執行緒的創建,對于這個方法不同平臺有不同的實作,這里不再贅述,

- 上面都是創建程序,之后再呼叫
Thread::start(native_thread);在 JVM_StartThread 中呼叫,該方法的實作在Thread.cpp中

start方法中有一個函式呼叫:os::start_thread(thread);,呼叫平臺啟動執行緒的方法,最侄訓呼叫Thread.cpp檔案中的JavaThread::run()方法
3.2 執行緒的終止
3.2.1 通過標記位來終止執行緒
- 正常我們執行緒內的東西都是回圈執行的,那么我們實際需求中肯定也存在想在其他執行緒來停止當前執行緒的需要,這是后我們可以通過標記位來實作,所謂的標記為其實就是
volatile修飾的變數,著由它的可見性特性決定的,如下代碼就是依據volatile來實作標記位停止執行緒
//定義標記為 使用 volatile 修飾
private static volatile boolean mark = false;
@Test
public void markTest(){
new Thread(() -> {
//判斷標記位來確定是否繼續進行
while (!mark){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒執行內容中...");
}
}).start();
System.out.println("這是主執行緒走起...");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//10秒后將標記為設定 true 對執行緒可見,用volatile 修飾
mark = true;
System.out.println("標記位修改為:"+mark);
}
3.2.2 通過 stop 來終止執行緒
- 我們通過查看
Thread類或者JDK API可以看到關于執行緒的停止提供了stop(),supend(),resume()等方法,但是我們可以看到這些方法都被標記了@Deprecated也就是過時的, - 雖然這幾個方法都可以用來停止一個正在運行的執行緒,但是這些方法都是不安全的,都已經被拋棄使用,所以在我們開發中我們要避免使用這些方法,關于這些方法為什么被拋棄以及導致的問題
JDK檔案中較為詳細的描述 《Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?》 - 在其中有這樣的描述:

-
總的來說就是:
- 呼叫
stop()方法會立刻停止run()方法中剩余的全部作業,包括在catch或finally等陳述句中的內容,并拋出ThreadDeath例外(通常情況下此例外不需要顯示的捕獲),因此可能會導致一些作業的得不到完成,如檔案,資料庫等的關閉, - 呼叫
stop()方法會立即釋放該執行緒所持有的所有的鎖,導致資料得不到同步,出現資料不一致的問題,
- 呼叫
3.2.3 通過 interrupt 來終止執行緒
- 通過上面闡述,我們知道了使用
stop方法是不推薦的,那么我們用什么來更好的停止執行緒,這里就引出了interrupt方法,我們通過呼叫interrupt來中斷執行緒 - 當其他執行緒通過呼叫當前執行緒的
interrupt方法,表示向當前執行緒打個招呼,告訴他可以中斷執行緒的執行了,至于什么時候中斷,取決于當前執行緒自己 - 執行緒通過檢查自身是否被中斷來進行相應,可以通過
isInterrupted()來判斷是否被中斷,
我們來看下面代碼:
public static void main(String[] args) {
//創建 interrupt-1 執行緒
Thread thread = new Thread(() -> {
while (true) {
//判斷當前執行緒是否中斷,
if (Thread.currentThread().isInterrupted()) {
System.out.println("執行緒1 接收到中斷資訊,中斷執行緒...");
break;
}
System.out.println(Thread.currentThread().getName() + "執行緒正在執行...");
}
}, "interrupt-1");
//啟動執行緒 1
thread.start();
//創建 interrupt-2 執行緒
new Thread(() -> {
int i = 0;
while (i <20){
System.out.println(Thread.currentThread().getName()+"執行緒正在執行...");
if (i == 8){
System.out.println("設定執行緒中斷....");
//通知執行緒1 設定中斷通知
thread.interrupt();
}
i ++;
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"interrupt-2").start();
}
列印結果如下:

上述代碼中我們可以看到,我們創建了
interrupt-1執行緒,其中用interrupt來判斷當前執行緒是否處于中斷狀態,如果處于中斷狀態那么就自然結束執行緒,這里的結束的具體操作由我們開發者來決定,再創建interrupt-2執行緒,代碼相對簡單不闡述,當執行到某時刻時將執行緒interrupt-1設定為中斷狀態,也就是通知interrupt-1執行緒,
執行緒中斷標記復位 :
在上述
interrupt-1代碼中如果加入sleep方法,那么我們會發現程式報出InterruptedException錯誤,同時,執行緒interrupt-1也不會停止,這里就是因為中斷標記被復位了 ,下面我們來介紹一下關于中斷標記復位相關的內容
- 在執行緒類中提供了** **
Thread.interrupted的靜態方法,用來對執行緒中斷標識的復位,在上面的代碼中,我們可以做一個小改動,對interrupt-1執行緒創建的代碼修改如下:
//創建 interrupt-1 執行緒
Thread thread = new Thread(() -> {
while (true) {
//判斷當前執行緒是否中斷,
if (Thread.currentThread().isInterrupted()) {
System.out.println("執行緒1 接收到中斷資訊,中斷執行緒...中斷標記:" + Thread.currentThread().isInterrupted());
Thread.interrupted(); // //對執行緒進行復位,由 true 變成 false
System.out.println("經過 Thread.interrupted() 復位后,中斷標記:" + Thread.currentThread().isInterrupted());
//再次判斷是否中斷,如果是則退出執行緒
if (Thread.currentThread().isInterrupted()) {
break;
}
}
System.out.println(Thread.currentThread().getName() + "執行緒正在執行...");
}
}, "interrupt-1");
上述代碼中 我們可以看到,判斷當前執行緒是否處于中斷標記為
true, 如果有其他程式通知則為true此時進入if陳述句中,對其進行復位操作,之后再次判斷,執行代碼后我們發現interrupt-1執行緒不會終止,而會一直執行
Thread.interrupted進行執行緒中斷標記復位是一種主動的操作行為,其實還有一種被動的復位場景,那就是上面說的當程式出現InterruptedException例外時,則會將當前執行緒的中斷標記狀態復位,在拋出例外前,JVM會將中斷標記isInterrupted設定為false
在程式中,執行緒中斷復位的存在實際就是當前執行緒對外界中斷通知信號的一種回應,但是具體回應的內容有當前執行緒決定,執行緒不會立馬停止,具體是否停止等都是由當前執行緒自己來決定,也就是開發者,
3.3 執行緒終止 interrupt 的原理
- 首先我們先來看一下在
Thread中關于interrupt的定義:
public void interrupt() {
if (this != Thread.currentThread()) {
checkAccess(); //校驗是否有權限來修改當前執行緒
// thread may be blocked in an I/O operation
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
// <1> 呼叫 native 方法
interrupt0(); // set interrupt status
b.interrupt(this);
return;
}
}
}
// set interrupt status
interrupt0();
}
- 上面代碼中我們可以看到,在
interrupt方法中最終呼叫了Native方法interrupt0,這里相關在執行緒啟動時說過,不再贅述,我們直接找到hotspot中jvm.cpp檔案中JVM_Interrupt方法

JVM_Interrupt方法比較簡單,其中我們可以看到直接呼叫了Thread.cpp的interrupt方法,我們進入其中查看

- 我們可以看到這里直接呼叫了
os::interrupt(thread)這里是呼叫了平臺的方法,對于不同的平臺實作是不同的,我們這里如下所示,選擇Linux下的實作os_linux.cpp中,

在上面代碼中我們可以看到,在
1處拿到OSThread,之后判斷如果interrupt為false則在2處呼叫OSThread的set_interrupted方法進行設定,我們可以進入看一下其實作,發現在osThread.hpp中定義了一個成員變數volatile jint _interrupted;而set_interrupted方法其實就是將_interrupted設定為true,之后再通過ParkEvent的unpark()方法來喚醒執行緒,具體的程序在上面進行的簡單的注釋介紹,

本文由AnonyStar 發布,可轉載但需宣告原文出處,
仰慕「優雅編碼的藝術」 堅信熟能生巧,努力改變人生
歡迎關注微信公賬號 :云棲簡碼 獲取更多優質文章
更多文章關注筆者博客 :云棲簡碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/166741.html
標籤:Java
下一篇:mybatis_3CRUD操作
