在併發編程中,我們需要處理兩個關鍵問題:
1. 線程之間如何通信
通信是指線程之間以何種機制來交換信息,在命令式編程(c語言)中,線程之間的通信機制有兩種:
1.1 共享內存
在共享內存的併發模型裏,線程之間共享程序的公共狀態,線程之間通過寫-讀內存中的公共狀態來隱式進行通信。
JAVA併發採用的是共享內存模型,java線程之間的通信總是隱式進行,整個通信過程對程序員完全透明。
1.2 消息傳遞
在消息傳遞的併發模型裏,線程之間沒有公共狀態,線程之間必須通過明確的發送消息來進行通信
2. 線程之間如何同步
同步是指程序用於控制不同線程之間操作發生相對順序的機制。
2.1 在共享內存併發模型裏,同步是顯示進行的
2.2 在消息傳遞的併發模型裏,由於消息的發送必須在消息的接收之前,因此同步是隱式的
共享變量 = 實例域(對象)、靜態域和數組元素
共享變量存儲在堆內存中,而堆內存在線程之間共享
局部變量,方法定義參數,異常處理參數不會在線程之間共享
它們不會有內存可見性問題,也不受內存模型的影響
java線程之間的通信由JMM控制
JMM決定一個線程對共享變量的寫入何時對另一個線程可見
MM定義了線程和主內存之間的抽象關係:
線程之間的共享變量存儲在主內存中
每個線程都有一個私有的本地內存,本地內存中存儲了該線程以讀/寫共享變量的副本,本地內存是JMM的一個抽象概念,並不真實存在。
它涵蓋了緩存,寫緩衝區,寄存器以及其他的硬件和編譯器優化。
JMM的抽象示意圖:
從上圖來看,線程A和線程B之間要通信的話,需要經歷下面2個步驟:
1. 線程A把本地內存中更新過的共享變量刷新到主內存中去
2. 線程B到主內存中去讀取線程A之前已更新過的共享變量
JMM通過控制主內存與每個線程的本地內存之間的交互來爲java程序員提供內存可見性保證
JAVA還有種方式可以用於線程間通信——管道流
操作和普通IO並無二致,只是需要把管道連接起來
PipedOutputStream內部使用PipedInputStream接收需要輸出的字節
用管道流實現生產者和消費者模式:
生產者:
public class Producer implements Runnable {
private PipedOutputStream pos;
public Producer(PipedOutputStream pos) {
this.pos = pos;
}
@Override
public void run() {
try {
pos.write("Hello World".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
消費者:
public class Consumer implements Runnable {
private PipedInputStream pis;
public Consumer(PipedInputStream pis) {
this.pis = pis;
}
@Override
public void run() {
// 將數據保存在byte數組中
byte[] bytes = new byte[100];
try {
// 從數組中得到實際大小。
int length = pis.read(bytes);
System.out.println(new String(bytes, 0, length));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
測試類:
public class TestPipedStream {
public static void main(String[] args) {
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream();
try {
// 連接管道
pos.connect(pis);
new Thread(new Producer(pos)).start();
new Thread(new Consumer(pis)).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
運行可以看到Hello World