前言
搞清楚Java中線程都有哪些狀態,線程間如何進行協作,這是使用Java進行并發編程的基礎。本文是作者自己對Java中線程的狀態、線程間協作、相關API使用的理解與總結,不對之處,望指出,共勉。
線程的狀態
-
NEW
新建狀態,表示線程已創建,但未啟動。 -
RUNNABLE
就緒狀態,表示線程已被JVM執行,但是可能還沒獲得操作系統CPU資源的調度,需要等待一會兒(轉瞬之間)。 -
BLOCKED
阻塞狀態,表示線程被阻塞,可能正在嘗試進入synchronized
關鍵字修飾的同步代碼塊、方法,等待獲取監視器鎖(monitor )。 -
WAITING
等待狀態,通常調用Object.wait()會進入該狀態,直到其他線程調用Object.notify()
或Object.notifyAll()
才會喚醒。 -
TIMED_WAITING
有限時間的等待狀態,通常調用Object.wait(long timeout)
或Object.wait(long timeout, int nanos)
會進入該狀態,與等待狀態類似,區別就是時間到了之后如果沒有被Object.notify()
或Object.notifyAll()
喚醒的話會自動喚醒。 -
TERMINATED
終止狀態,線程已經執行完畢。
線程的協作
線程間的協作主要通過java.lang.Object
的成員方法wait()
、notify()
、notifyAll()
和java.lang.Thread
的靜態方法sleep(long millis)
、yield()
、join()
等進行。
-
wait()
使一個線程處于等待狀態,并且釋放所持有的對象的監視器鎖,進入等待池直到notify或notifyAll方法來喚醒。調用此方法要處理java.lang.InterruptedException
異常。 -
wait(long timeout)
如上,區別在于可以自行設置最大等待時間(毫秒),到時間沒有被喚醒則自動喚醒。 -
wait(long timeout, int nanos)
如上,提供更精確的等待時間(納秒)。
public class WaitTest {
public synchronized void work() {
System.out.println("Begin Work");
try {
//等待1000毫秒后,自動喚醒繼續執行
wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Work End");
}
public static void main(String[] args) {
WaitTest test = new WaitTest();
new Thread(() -> test.work()).start();
}
}
-
notify()
喚醒一個處于等待狀態的線程,當然在調用此方法的時候,并不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且與優先級無關。 -
notifyAll()
喚醒所有處于等待狀態的線程,該方法并不是將對象的監視器鎖給所有線程,而是讓它們去競爭鎖,只有獲得鎖的線程才能進入就緒狀態。
public class NotifyTest {
public synchronized void work() {
System.out.println("Begin Work");
try {
//等待喚醒
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Work End");
}
public synchronized void continueWork() {
notifyAll();
}
public static void main(String[] args) throws InterruptedException {
NotifyTest test = new NotifyTest();
new Thread(() -> test.work()).start();
//等待3000毫秒后喚醒,繼續工作。
Thread.sleep(3000);
test.continueWork();
}
}
注意:wait()
、notify()
、notifyAll()
的實現依賴于monitor,所以這些方法必須運行在被synchronized
關鍵字修飾的方法或代碼塊中(因為使用synchronized
關鍵字可以獲得monitor,詳見上一篇文章),否則在運行時會拋出java.lang.IllegalMonitorStateException
異常,你可以嘗試去掉上面代碼中的synchronized
關鍵字,再執行代碼試試。
- 生產者-消費者并發模型
有一群生產者進程在生產產品,并將這些產品提供給消費者進程去消費。為使生產者進程與消費者進程能并發執行,在兩者之間設置了一個具有n個緩沖區的緩沖池,生產者進程將它所生產的產品放入一個緩沖區中;消費者進程可從一個緩沖區中取走產品去消費。盡管所有的生產者進程和消費者進程都是以異步方式運行的,但它們之間必須保持同步,即不允許消費者進程到一個空緩沖區去取產品,也不允許生產者進程向一個已裝滿產品且尚未被取走的緩沖區投放產品。
生產者-消費者并發模型是并發編程中一個經典的模型,最早由Dijkstra提出,用以演示它提出的信號量機制。在并發編程中使用該模型能夠解決絕大多數并發問題。該模式通過平衡生產線程和消費線程的工作能力來提高程序的整體處理數據的速度。可以通過wait()
、notifyAll()
來實現該模型。
public class ProducerConsumerModelWaitNotifyImpl {
public static void main(String[] args) {
List<Product> buffer = new LinkedList<Product>();
ExecutorService es = Executors.newFixedThreadPool(5);
//兩個生產者
es.execute(new Producer(buffer));
es.execute(new Producer(buffer));
//三個消費者
Consumer consumer = new Consumer(buffer);
es.execute(new Consumer(buffer));
es.execute(new Consumer(buffer));
es.execute(new Consumer(buffer));
es.shutdown();
/**
輸出:
...
生產者[pool-1-thread-2]生產了一個產品:iPhone 手機
生產者[pool-1-thread-2]生產了一個產品:iPhone 手機
消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
生產者[pool-1-thread-1]生產了一個產品:iPhone 手機
消費者[pool-1-thread-4]消費了一個產品:iPhone 手機
生產者[pool-1-thread-2]生產了一個產品:iPhone 手機
...
*/
}
//產品
static class Product {
private String name;
public Product(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
//生產者
static class Producer implements Runnable {
private final int maxSize = 10;//產品最大庫存量
private List<Product> buffer;
public Producer(List<Product> buffer) {
this.buffer = buffer;
}
public void run() {
while (true) {
synchronized (buffer) {
while (buffer.size() >= maxSize) {
try {
buffer.wait();//將當前線程放入等鎖(buffer對象的鎖)池,并釋放鎖。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//模擬生產需要500毫秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product product = new Product("iPhone 手機");
buffer.add(product);
System.out.println("生產者[" + Thread.currentThread().getName() + "]生產了一個產品:" + product);
buffer.notifyAll();//生產完畢通知等待池內的其他線程(生產者或消費者都有可能)
}
}
}
}
//消費者
static class Consumer implements Runnable {
private List<Product> buffer;
public Consumer(List<Product> buffer) {
this.buffer = buffer;
}
public void run() {
while (true) {
synchronized (buffer) {
while (buffer.isEmpty()) {
try {
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消費者[" + Thread.currentThread().getName() + "]消費了一個產品:" + buffer.remove(0));
buffer.notifyAll();//消費完畢通知等待池內的其他線程(生產者或消費者都有可能)
}
}
}
}
}
-
sleep(long millis)
使一個正在運行的線程處于“睡眠狀態”,sleep()方法只是暫時讓出CPU的執行權,并不會釋放監視器鎖,調用此方法要處理java.lang.InterruptedException
異常。關于該方法還有個段子代碼,如下。
public class Interceptor {
void after(){
//讓所有接口都慢一點,產品經理下次讓你優化性能的時候你減一點睡眠時間就OK了。。。
Thread.sleep(100);
}
}
-
sleep(long millis, int nanos)
如上,時間單位精確到納秒。 -
yield()
使一個正在運行的線程暫停執行(讓出CPU)讓其他線程執行,即回到就緒狀態。
public class YieldTest {
public void work(){
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " Working");
Thread.yield();
}
}
public static void main(String[] args) {
YieldTest test = new YieldTest();
new Thread(() -> test.work()).start();
new Thread(() -> test.work()).start();
/**
輸出:
Thread-0 Working
Thread-1 Working
Thread-0 Working
Thread-1 Working
Thread-0 Working
Thread-1 Working
**/
}
}
-
join()
使主線程等待子線程執行完成后再執行,換句話說就是將線程的并行執行變為串行執行。
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + " Work End"));
thread1.start();
thread1.join();//合并到主線程,主線程將等待該子線程執行完畢才會執行
Thread thread2 = new Thread(() -> System.out.println(Thread.currentThread().getName() + " Work End"));
thread2.start();
thread1.join();//合并到主線程,主線程將等待該子線程執行完畢才會執行
System.out.println("Main Thread Work End");
/**
輸出:
Thread-0 Work End
Thread-1 Work End
Main Thread Work End
不使用join():
Main Thread Work End
Thread-0 Work End
Thread-1 Work End
*/
}
}
-
join(long millis)
如上,但是主線程只會等待一定的時間(毫秒),如果時間到了該子線程仍未執行完,則放棄等待,執行主線程自己的代碼。
public class TimedJoinTest{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
//模擬子線程需要執行500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " Work End");
});
thread.start();
thread.join(100);//合并到主線程,主線程將等待該子線程執行完畢才會執行,只等待100毫秒,過時不在等。
System.out.println("Main Thread Work End");
/**
輸出:
Main Thread Work End
Thread-0 Work End
刪除Thread.sleep(500);或者降到100以內:
Thread-0 Work End
Main Thread Work End
*/
}
}
-
join(long millis, int nanos)
如上,時間單位精確到納秒