java多執行緒-02-基本操作及執行緒通訊示例
宣告
該系列文章只是記錄本人回顧java多執行緒程式設計時候記錄的筆記。文中所用語言並非嚴謹的專業術語(太嚴謹的術語其實本人也不會……)。難免有理解偏差的地方,歡迎指正。
另外,大神請繞路。不喜勿噴。
畢竟好記性不如爛筆頭嘛,而且許多東西只要不是你經常用的最終都會一丟丟一丟丟地給忘記。
1 執行緒的相關概念
1.1 執行緒狀態
先看看下面這張來自百度的java執行緒狀態圖:
- 新建狀態(New)
用new語句建立的執行緒處於新建狀態,只是為他分配了記憶體,並沒有開始執行。
- 就緒狀態(Runnable)
呼叫start()方法,執行緒就進入就緒狀態。
然而也僅僅是處於可執行狀態,到底能不能立即執行還要看作業系統的排程。
- 執行狀態(Running)
此時是真正的處於執行狀態。是由可執行狀態到執行狀態。
- 阻塞狀態(Blocked)
執行緒由於某些原因放棄CPU的佔有權。當執行緒處於阻塞狀態時。直到下次該執行緒處於可執行狀態時才有可能再次進入執行狀態。
可能的阻塞原因:
- 呼叫了wait方法,進入了等待池。
- 等待某些資源可用。
- I/O等待、呼叫了sleep方法、其他執行緒的join方法等。
- 死亡狀態(Dead)
執行緒執行結束。注意一個已經死亡的執行緒是沒辦法“復活”的。只能重新new一個。
1.2 優先順序
大多數資料都顯示,java中的執行緒優先順序並不會生效,往往會被作業系統直接忽略。
所以,此處對這部分內容直接略過。
1.3 daemon執行緒
daemon執行緒又稱為後臺執行緒或精靈執行緒。
可以通過setDaemon(boolean isDeamon)
將一個執行緒設定為後臺執行緒。比如java的垃圾回收執行緒。
注意:
- 當JVM中不存在非daemon執行緒的時候,JVM就會退出。
- 將一個執行緒設定為Daemon執行緒,必須在其啟動之前進行設定。
1.4 中斷
java執行緒的中斷表示的是:某個執行緒是否被其他執行緒中斷過。相當於一個boolean型別的標識位。
此處的中斷過類似於你正在寫程式碼,突然有人找你說話,此處可以理解為被中斷了。
isInterrupted()
Thread.interrupted()
:復位中斷標誌
執行緒被中斷後丟擲java.lang.InterruptedException
異常,但是在異常被丟擲之前,JVM會先將中斷標識位復位。也就是說,在丟擲java.lang.InterruptedException
異常之後並且在下次中斷之前呼叫isInterrupted()
方法會返回false。
1.5 suspend resume stop
這三個API都是deprecated
的。已經被廢棄。此處提出來只是為了知識點的完整性。
suspend()
:暫停(睡眠)執行緒resume()
:恢復執行緒stop()
:終止執行緒
在《java併發程式設計的藝術》一書中,作者舉了個很形象的例子來說明這三個方法的作用:CD機播放音樂時暫停(suspend)、恢復(resume)和停止(stop)的操作。同時,作者也說明了這三個人性化
的方法之所以過時是因為:
- suspend方法會一致佔有著擁有的資源進入睡眠狀態
- stop方法在終止的執行緒的時候對資源的釋放沒有保證
1.6 物件監視器
任何物件都有自己的監視器。當某個物件被同步方法或者同步塊呼叫時,執行方法的執行緒必須先獲得其監視器。
沒有獲取到監視器的執行緒將被阻塞在方法的入口處進入阻塞(blocked)狀態。
所以,任何物件都可以被當做鎖來使用。
1.7 wait()和sleep()
- 兩者都可以讓執行緒等待
- wait()呼叫時,必須先獲取到鎖
- wait()是Object的方法
- sleep()是Thread的方法
2 執行緒通訊
一般而言,執行緒通訊至少有兩種方式:訊息傳遞和共享記憶體。
在java裡一般是共享記憶體實現的執行緒通訊。當然也有第三方的訊息傳遞模型的執行緒通訊。
下文所說的執行緒通訊指的是共享記憶體模型。
2.1 基本概念
和執行緒通訊相關的方法
method | DESC |
---|---|
notify() | 通知一個在物件上等待的執行緒,使其從wait()方法返回,返回的前提是該執行緒獲取到了物件的鎖 |
notifyAll() | 通知所有等待在該物件上的執行緒 |
wait() | 呼叫wait方法的執行緒將釋放鎖(如果已經持有鎖的話)並進入waiting狀態。只有等待其他執行緒的通知或被中斷才會返回。 |
wait(long t) | 和wait方法的不同是:等待t毫秒還是沒有被喚醒的話會“超時返回” |
wait(long,int) | 對超時時間控制更加細緻的wait(long)版本 |
join() | 執行緒T呼叫tx.join()的意思是:T等待tx執行緒結束後才從tx.join()返回,接著執行T自己的後續程式碼 |
幾個注意點
- 呼叫wait、notify、notifyAll時必須先獲得鎖
- 呼叫wait方法後,執行緒有running狀態–>waitting狀態,並將當前執行緒至於物件的等待池中。
- 呼叫notify或者notifyAll方法後waiting狀態的執行緒能夠返回的前提是:呼叫notifyAll或notify方法的執行緒先釋放鎖,並且該執行緒獲得了鎖。
- notify呼叫後,會將等待池(等待佇列)中的一個執行緒移動到同步佇列中。被移動的執行緒狀態:waiting–>blocked。
- notifyAll方法呼叫後,會將等待池中所有的執行緒移動至同步佇列中。被移動的執行緒狀態:waiting–>blocked。
join()的一個有趣的例子
* 注意:該示例來自《java併發程式設計的藝術》一書*
import java.util.concurrent.TimeUnit;
public class Join {
public static void main(String[] args) throws Exception {
Thread previous = Thread.currentThread();
for (int i = 0; i < 10; i++) {
// 每個執行緒擁有前一個執行緒的引用,需要等待前一個執行緒終止,才能從等待中返回
Thread thread = new Thread(new Runner(previous), String.valueOf(i));
thread.start();
previous = thread;
}
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + " terminate.");
}
static class Runner implements Runnable {
private Thread previous;
public Runner(Thread thread) {
this.previous = thread;
}
public void run() {
try {
previous.join();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
}
以上示例的結果其實就是所有執行緒依次序列執行。
2.2 執行緒同步示例
此處是本人改寫的一個生產者和消費者的案例。
生產者不斷生成,消費者不斷消費。
public class ProducerConsumer {
public static void main(String[] args) {
Container container = new Container(5);
for (int i = 0; i < 10; i++) {
new Thread(new Producer(container), "P-" + i).start();
new Thread(new Consumer(container), "C-" + i).start();
}
}
public static class Product {
private String name;
public Product(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "[name=" + name + "]";
}
}
public static class Container {
private int nextIndex = 0;
Product[] products = null;
public Container(int size) {
this.products = new Product[size > 0 ? size : 5];
}
public void push(Product product) {
synchronized (products) {
while (this.nextIndex >= this.products.length) {
try {
// 訪問products的執行緒先wait
this.products.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 叫醒在products等待池上等待的執行緒
this.products.notifyAll();
this.products[nextIndex++] = product;
}
}
public Product pop() {
synchronized (products) {
while (this.nextIndex <= 0) {
try {
this.products.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 叫醒在products等待池上等待的執行緒
this.products.notifyAll();
return this.products[--nextIndex];
}
}
}
public static class Producer implements Runnable {
private Container container;
public Producer(Container container) {
super();
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
Product p = new Product(Thread.currentThread().getName() + "_" + i);
this.container.push(p);
System.out.println("生產者[" + Thread.currentThread().getName() + "]生產>>>>>>:" + p);
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static class Consumer implements Runnable {
private Container container;
public Consumer(Container container) {
super();
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
Product product = this.container.pop();
System.out.println("消費者[" + Thread.currentThread().getName() + "]消費<<<<<<:" + product);
try {
Thread.sleep((long) (Math.random() * 2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
參考資料
- 《java併發程式設計的藝術》