一、前言
在上一篇博客中,小編向大家介紹了 線程創建和啓動,簡單的向大家介紹了線程創建和啓動的三種方法:1.繼承Thread類,2.實現Runnable接口,3.使用Future和Task創建。可能大家對線程的初步創建有了一定的瞭解。
在這篇博客中,小編向大家介紹一下,線程運行起來後,有的狀態。
二、線程的狀態
線程在執行過程中,可以處於下面幾種狀態:
就緒(Runnable):線程準備運行,不一定立馬就能開始執行。
運行中(Running):進程正在執行線程的代碼。
等待中(Waiting):線程處於阻塞的狀態,等待外部的處理結束。
阻塞狀態(Blocked):等待 I/O 操作完成。
同步阻塞(Locking):等待獲取鎖。
死亡(Dead):線程完成了執行。
下面詳細說明:
- 就緒狀態 - Runnable
當我們創建好線程後,Thread t = new Thread();
,開始調用t.start();
方法後,線程就從初始狀態轉變爲就緒狀態。
就緒狀態下的線程,在等待操作系統調用,操作系統會根據不同的調度算法來進行調度線程。比如操作系統使用時間片輪轉算法。
- 運行狀態 - Running
當操作系統選定要運行的線程後,這個線程就會從就緒狀態轉爲運行狀態。這個時候,運行狀態的線程就會執行我們重寫的run()
方法。
- 阻塞狀態 - blocked
有的時候,我們需要給線程之間一些緩衝時間,通常使用Sleep()
讓子線程和主線程錯開。有的時候我們需要線程按照一定的順序執行,這個時候我們可以使用b.join()
,安排在線程b執行完成後再執行。
所以在sleep()
和join()
調用的過程中,線程會處於阻塞狀態。只有等sleep()
和join()
完成後,線程纔會再次進入就緒狀態,等待cpu調用。
- 等待狀態 - waiting
運行的線程執行wait()方法,該線程會釋放佔用的所有資源,JVM會把該線程放入“等待池”中。進入這個狀態後,是不能自動喚醒的,必須依靠其他線程調用notify()或notifyAll()方法才能被喚醒
- 鎖池狀態 - locking
當執行中的線程進入synchronized同步塊的時候,沒有獲取到鎖的線程,就會進入鎖池狀態,獲取到鎖的線程執行完成後,鎖解除,進入就緒狀態。
- 死亡狀態 -dead
線程執行完成,進入死亡狀態。
三、一些知識點
在上面的線程狀態圖中,裏面有很多方法,可能不是很清楚。下面小編通過幾個問題來對比記憶一下這些方法:
java多線程中,會有很多問題,比如經典的:
3.1 現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行?
【方法一】
這個簡單的問題,我們可以直接使用join函數完成,join的作用就是,b.join()
在b線程執行完成後再執行。這樣就保證了執行順序。
package com.dmsd.thread;
/**
* Created by Ares on 2018/6/11.
*
* 說明:使用join完成 ————t1執行完了,t2執行;t2執行完成後,t3執行。
*
* 假設現在有兩個線程A、B。如果在A的run方法中調用B.join(),
* 表示A需要在B線程上面等待,也就是需要在B線程執行完成之後才能再次執行。
*/
public class join {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 run:"+i);
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(40);
t1.join();//表明當前線程需要在t1線程上等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 run:"+i);
}
});
Thread t3 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(40);
t2.join();//表明當前線程需要在t2線程上等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3 run:"+i);
}
});
t1.start();
t2.start();
t3.start();
}
}
【方法二】
可以說使用join的老鐵,還是比較low的。在我們的juc包( java.util.concurrent)中,利用ExcutorService產生的newSingleThreadExecutor的單一線程池。這個線程池的底層是使用了先進先出的隊列,通過submit依次把線程添加進隊列,然後順序執行。
package com.dmsd.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by Ares on 2018/8/14.
*/
public class TestSingleThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Thread t1 = new Thread(new MyThread1());
Thread t2 = new Thread(new MyThread2());
Thread t3 = new Thread(new MyThread3());
executorService.submit(t1);
executorService.submit(t2);
executorService.submit(t3);
executorService.shutdown();
}
}
class MyThread1 implements Runnable {
@Override
public void run() {
System.out.println("I am thread 1");
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("I am thread 2");
}
}
class MyThread3 implements Runnable {
@Override
public void run() {
System.out.println("I am thread 3");
}
}
底層中用了newThreadPoolExecutor()創建了線程池,線程池中一個線程,並把線程放到linkedBlockingQueue阻塞隊列中。保證了線程的順序執行。
源碼:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
3.2 Java中sleep和wait的區別 ?
這個問題也是經常問的問題。
① 這兩個方法來自 不同的類 分別是,sleep來自Thread類,和wait來自Object類。
sleep是Thread的靜態類方法, 誰調用的誰去睡覺,即使在a線程裏調用b的sleep方法,實際上還是a去睡覺, 要讓b線程睡覺要在b的代碼中調用sleep。
② 鎖: 最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
sleep不出讓系統資源;wait是進入線程等待池等待,出讓系統資源,其他線程可以佔用CPU。一般wait不會加時間限制,因爲如果wait線程的運行資源不夠,再出來也沒用,要等待其他線程調用notify/notifyAll喚醒等待池中的所有線程,纔會進入就緒隊列等待OS分配系統資源。sleep(milliseconds)可以用時間指定使它自動喚醒過來,如果時間不到只能調用interrupt()強行打斷。
Thread.sleep(0)的作用是“觸發操作系統立刻重新進行一次CPU競爭”。
③ 使用範圍:wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用。
synchronized(x){
x.notify()
//或者wait()
}
四、小結
通過線程的狀態,詳細大家可以更加深入的剝開多線程的面紗,對多線程的理解也會更加的深刻。