1. Java多線程基本概念

多線程的優(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í)行困難。
  1. 函數式并行

Same-threading

Same-threading是由多個單線程系統(tǒng)組成,每個單線程系統(tǒng)之間不共享數據。

幾種線程系統(tǒng)之間的比較

并行和并發(fā)

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

Concurrency

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

Parallelism

競爭條件(Race Condition)和關鍵區(qū)域(Critical Sections)

線程對關鍵區(qū)域的數據進行競爭,競爭的結果影響執(zhí)行關鍵區(qū)域的結果

當多個線程對關鍵區(qū)域里面的相同資源進行操作時,會產生問題。其實只有多個線程進行寫操作的時候才會產生問題。

當兩個線程競爭同一資源時,如果對資源的訪問順序敏感,就稱存在競爭條件(race condition)
導致競爭條件發(fā)生的代碼區(qū)稱作臨界區(qū)(critical section)

解決競爭條件的方法:

  1. synchronized
  2. lock
  3. 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);
  }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容