多線程的優(yōu)點
- 資源利用率更好(等待IO的時間)
- 程序設計在某些情況下更簡單(一個線程對應一個任務)
- 程序相應更快(不用實時去相應)
多線程的代價
- 設計更復雜(訪問共享數據的機制)
- 上下文切換的開銷
- 增加資源消耗(每個線程本身占用內存資源)
并發(fā)編程模型
并發(fā)工作者
傳入的作業(yè)會被分配到不同的工作者上。

- 優(yōu)點:理解簡單,可以通過增加工作者提高系統(tǒng)的并行度
- 缺點:共享狀態(tài)復雜、工作者無狀態(tài)、任務的順序不確定
流水線模式
每個工作者只負責作業(yè)的部分工作,當完成了這部分工作時的工作者會將作業(yè)轉發(fā)給下一個工作者

- 優(yōu)點:無需共享狀態(tài)、有狀態(tài)的工作者、更好的硬件整合,合理的工作順序
- 缺點:作業(yè)的執(zhí)行分布在多個工作者上,追蹤某個作業(yè)到底被什么代碼執(zhí)行困難。
- 函數式并行
Same-threading
Same-threading是由多個單線程系統(tǒng)組成,每個單線程系統(tǒng)之間不共享數據。

并行和并發(fā)
并發(fā):應用同時處理多個任務,任務的開始不需要等另外一個任務結束。

并行:應用將一個任務分成多個小的子任務,每個子任務實例都是用一個CPU進行處理。

競爭條件(Race Condition)和關鍵區(qū)域(Critical Sections)
線程對關鍵區(qū)域的數據進行競爭,競爭的結果影響執(zhí)行關鍵區(qū)域的結果
當多個線程對關鍵區(qū)域里面的相同資源進行操作時,會產生問題。其實只有多個線程進行寫操作的時候才會產生問題。
當兩個線程競爭同一資源時,如果對資源的訪問順序敏感,就稱存在競爭條件(race condition)
導致競爭條件發(fā)生的代碼區(qū)稱作臨界區(qū)(critical section)
解決競爭條件的方法:
- synchronized
- lock
- atomic variable
在某些情況下可以嘗試分解同步塊的作用范圍
public class TwoSums {
private int sum1 = 0;
private int sum2 = 0;
private Integer sum1Lock = new Integer(1);
private Integer sum2Lock = new Integer(2);
public void add(int val1, int val2){
synchronized(this.sum1Lock){
this.sum1 += val1;
}
synchronized(this.sum2Lock){
this.sum2 += val2;
}
}
}
線程安全和共享資源
線程安全(thread safe)
當多個線程同時調用的時候,代碼是安全的。線程安全的代碼不包含競爭條件。競爭條件只在多個線程更新共同資源的時候發(fā)生。
共享資源的安全性(shared resources)
- 局部變量:存儲在每個線程的棧當中,是線程安全的。
public void someMethod(){
long threadSafeInt = 0;
threadSafeInt++;
}
- 局部對象引用:引用本身不共享,但是引用的對象是放在公共區(qū)域(堆)中的。如果對象不逃出創(chuàng)造它的方法,那么它也是線程安全的。
public void someMethod(){
LocalObject localObject = new LocalObject();
localObject.callMethod();
method2(localObject);
}
public void method2(LocalObject localObject){
localObject.setValue("value");
}
- 成員變量:成員變量隨著對象存儲在堆中,如果兩個線程調用同一對象的一個方法來更新這個成員變量,那么這個方法不是線程安全的。
public class NotThreadSafe{
StringBuilder builder = new StringBuilder();
public add(String text){
this.builder.append(text);
}
}
public class MyRunnable implements Runnable{
NotThreadSafe instance = null;
public MyRunnable(NotThreadSafe instance){
this.instance = instance;
}
public void run(){
this.instance.add("some text");
}
public static void main(String[] args) {
NotThreadSafe sharedInstance = new NotThreadSafe();
new Thread(new MyRunnable(sharedInstance)).start();
new Thread(new MyRunnable(sharedInstance)).start();
}
}
如果兩個線程調用add()方法的時候使用不同的實例對象,那么久不會產生競爭條件。
new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();
線程控制逃逸規(guī)則--判斷對某些資源的訪問是線程安全的
如果一個資源的創(chuàng)建,使用,銷毀都在同一個線程內完成,且永遠不會脫離該線程的控制,則該資源的使用就是線程安全的。
即使對象本身是線程安全的,如果該對象中包含的其他資源(文件,數據庫連接),整個應用也有可能不是線程安全的。
區(qū)分某個線程控制的對象是資源本身,還是僅僅到某個資源的應用很重要。
線程安全及不可變性
我們可以通過把共享的資源設為不可變的,使線程之間的共享資源永遠不能發(fā)生改變,因此是線程安全的。
public class ImmutableValue{
private int value = 0;
public ImmutableValue(int value){
this.value = value;
}
public int getValue(){
return this.value;
}
public ImmutableValue add(int valueToAdd){
return new ImmutableValue(this.value + valueToAdd);
}
}
但是即使對象是不可變的,但是指向該對象的引用仍然可以是線程不安全的。在使用不可變特性來保證線程安全的時候,特別需要注意。
public class Calculator{
private ImmutableValue currentValue = null;
public ImmutableValue getValue(){
return currentValue;
}
public void setValue(ImmutableValue newValue){
this.currentValue = newValue;
}
public void add(int newValue){
this.currentValue = this.currentValue.add(newValue);
}
}