目錄
概要:
- 什麼是同步,什麼是異步,什麼是互斥?
異步:異步是現在操作系統的共有的基本特性。熟悉計算機操作系統的小夥伴們應該知道,計算機的發展經過好幾個階段,這裏不長篇大論,簡單講講,計算機以前不支持多進程運行的,那個時候計算機同一時間段內只能處理一個作業,直到整個作業結束纔可以執行下一個作業,這裏會出現問題,比如說這臺計算機用cpu計算任務需要二十秒,而將任務結果用打印機打印出來需要一分鐘,按照這種計算機的特性,cpu在打印機結束之前不能用於別的進程。按照剛纔的例子就是cpu空閒了60-20=40秒,要知道cpu那麼貴,這是不能忍的。隨着發展,到分時系統的出現解決了這個問題,所謂分時系統就是cpu可以同時進行多個任務的處理,具體做法就是用時鐘中斷控制每一個進程的運行時間,簡單來說就是輪流運行,每個進程運行時間段是固定的,這個時間段很小,這樣可以保證cpu大多數時候都在多個進程之間來回切換,提高了cpu利用率。這種多進程共同執行的現象就是異步。
一:異步會帶來什麼問題?
加入在第一個時間片段A進程申請打印機資源,然後A進程放棄cpu資源讓給B,而A去用打印機了。此時B進程在自己的時間片段內也申請了打印機。這時打印機就會打印出A B兩個進程的運算結果,這就是異步帶來的資源分配問題。
二: 如何解決?
我們可以用一個鑰匙,誰要用打印機先出示鑰匙,沒有鑰匙對不起,打印機拒絕執行,這個思想就是說對於共享的臨界資源在同一時刻必須只能一個進程訪問,這就叫互斥,也就是互斥訪問。
說了這麼多,先總結兩個概念:
- 異步:系統本身的特性,後來在進程的基礎上操作系統有引入了線程,不過概念差不多,多進程之間異步操作,多線程之間也是異步操作。異步就是輪流去申請cpu資源。
- 互斥解決解決異步帶來的公共資源狀態不一致特性,讓臨界資源同一時刻只能由有一個線程訪問。
三:說了這麼多什麼是同步?
同步:解決程序之間因爲併發而存在的異步性 。 這句話的意思就是,因爲異步會讓線程之間輪流獲取時間片執行,誰在下一個時間片段獲取到cpu是不確定,又系統進行分配。那麼加入此有這麼一個需求,B線程必須在A線程運行結束之後纔可以執行,因爲B線程的執行依賴A線程的結果。這就要求先A後B,這種順序性的保證就叫做同步。
到這裏,估計你有點迷糊,這同步和互斥怎麼看着差不多啊? 沒錯,其實同步和互斥原理確實一樣,都是通過鑰匙來強行控制線程執行的。
四:總結
所以說,java多線程操作就是用互斥保證臨界資源的安全。也可以這麼說,java多線程操作就是保證線程之間對於臨界資源同步性。 細微區別就是,同步強調A->B絕對順序,而互斥一般是說這個資源必須保證安全,但是對於A進程B進程誰先用沒有概念上的限制。 所以說同步互斥底層原理都是一樣的,都是用原子特性(一般是硬件實現的 p v 操作)保證臨界資源的安全,因爲同步是說有順序的,一般對於線程通信我們說成同步操作。 而無序的稱爲互斥操作。
五:同步例子
下面是一個同步操作,你也可以說是java多線程安全的互斥操作(不嚴謹,也不錯)。
如果兩個線程之間有依
賴關係,線程之間必須進行通信,互相協調才能完成工作。
例如有一個經典的堆棧問題,一個線程生成了一些數據,將數據壓棧;另一個線程消費了這些數據,
將數據出棧。這兩個線程互相依賴,當堆棧爲空時,消費線程無法取出數據時,應該通知生成線程添
加數據;當堆棧已滿時,生產線程無法添加數據時,應該通知消費線程取出數據。wait noitfy兩個方法是每一個對象都有的方法,Object最上層的方法,可以對當前對象進行控制。
package wait;
//堆棧類
public class Stack {
// 堆棧指針初始值爲0
private int pointer = 0;
// 堆棧有5個字符的空間
private char[] data = new char[5];
// 壓棧方法,加上互斥鎖
public synchronized void push(char c) {
// 堆棧已滿,不能壓棧
while (pointer == data.length) {
try {
// 等待,直到有數據出棧
this.wait();
} catch (InterruptedException e) {
}
}
// 通知其他線程把數據出棧
this.notify();
// 數據壓棧
data[pointer] = c;
// 指針向上移動
pointer++;
}
// 出棧方法,加上互斥鎖
public synchronized char pop() {
// 堆棧無數據,不能出棧
while (pointer == 0) {
try {
// 等待其他線程把數據壓棧
this.wait();
} catch (InterruptedException e) {
}
}
// 通知其他線程壓棧
this.notify();
// 指針向下移動
pointer--;
// 數據出棧
return data[pointer];
}
}
package wait;
public class Main {
public static void main(String args[]) {
Stack stack = new Stack();
// 下面的消費者和生產者所操作的是同一個堆棧對象stack
// 生產者線程
Thread producer = new Thread(() -> {
char c;
for (int i = 0; i < 10; i++) {
// 隨機產生10個字符
c = (char) (Math.random() * 26 + 'A');
// 把字符壓棧
stack.push(c);
// 打印字符
System.out.println("生產: " + c);
try {
// 每產生一個字符線程就睡眠
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
}
});
// 消費者線程
Thread consumer = new Thread(() -> {
char c;
for (int i = 0; i < 10; i++) {
// 從堆棧中讀取字符
c = stack.pop();
// 打印字符
System.out.println("消費: " + c);
try {
// 每讀取一個字符線程就睡眠
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
}
});
producer.start(); // 啓動生產者線程
consumer.start(); // 啓動消費者線程
}
}
篇幅有限:實在不理解的建議看看操作系統。