2017-06-02 90 views
3

我學習線程和整個這個片段這裏走來:爲什麼異步線程不能同時修改ArrayList?

我們創建並啓動兩個相同java.lang.Thread S和讓他們不斷地修改0​​而不做任何有關這是非線程安全的,因爲我們要做的僅僅是研究。

這兩個線程只是同一類NoteThread的實例。在run()方法有兩種操作:

  1. add(item)從列表中選擇列表
  2. remove(0)

這兩個操作在1000次迭代中執行。

public class Solution { 
    public static void main(String[] args) { 
     new NoteThread().start(); 
     new NoteThread().start(); 
    } 

    public static class Note { 

     public static final List<String> notes = new ArrayList<String>(); 

     public static void addNote(String note) { 
      notes.add(0, note); 
     } 

     public static void removeNote(String threadName) { 
      String note = notes.remove(0); 
      if (note == null) { 
       System.out.println("Another thread has already deleted the note"); 
      } else if (!note.startsWith(threadName)) { 
       System.out.println("Thread [" + threadName + "] has deleted [" + note + "]"); 
      } 
     } 
    } 

    public static class NoteThread extends Thread{ 
     @Override 
     public void run() { 
      for (int i = 0; i < 1000; i++) { 
       Note.addNote(getName() + "-Note" + i); 
       Note.removeNote(getName()); 
      } 
     } 
    } 
} 

有時,它可以拋出IndexOutOfBoundsException: Index: 0, Size: -1當列表是空的,我不明白怎麼說都可以。

輸出的一個例子:

 
Thread [Thread-1] has deleted [Thread-0-Note597]   
Another thread has already deleted the note   
Thread [Thread-0] has deleted [Thread-1-Note558]   
Another thread has already deleted the note   
Thread [Thread-1] has deleted [Thread-0-Note635]   
Another thread has already deleted the note   
Thread [Thread-0] has deleted [Thread-1-Note580]

我們可以100%肯定,認爲項目的創作總是發生它是缺失一個相同的線程之前,所以我認爲這是不可能遇到一個當一個線程想要刪除一個項目但沒有找到一個項目的情況。

UPDATESergey Rybalkin已經非常清楚地解釋一個綱領性的執行順序的概念(這是我原來的問題沒有提及,但意思也無妨),最重要的是,他已經回答了這個問題:

如果Thread 1增加了一些內容,Thread 2在某些情況下不會看到 中的更改。

在Java中,我們修改的每個對象實際上在每個線程中都有一個緩存副本,可以與它一起工作。因爲這個例子沒有對線程安全做任何事情,我們修改的數組也被緩存到每個線程。現在,既然如此,存在這樣的可能性:

注意,這只是我的理解,我不是專家

  1. Thread 1複製到陣列的緩存。
  2. Thread 2將數組複製到其緩存中。
  3. Thread 1將項目添加到其緩存陣列。
  4. Thread 2將項目添加到其緩存陣列。
  5. Thread 1從其緩存陣列中刪除項目。將其緩存的數組刷新到實際的數組中。
  6. JVM傳播更改並將實際數組上載到該對象的所有用戶 - 到Thread 2。所以,第二個線程現在擁有當前爲空的數組的更新版本。
  7. Thread 2從其緩存陣列中刪除項目。
  8. 異常:列表已經爲空:IndexOutOfBoundsException: Index: 0,Size: -1
+0

p.s.我知道你不應該'延長線程' – Sam

+0

你想同步嗎?因爲兩個線程都訪問相同的資源,即註釋 –

+1

在同一個列表的併發修改之後,你期望什麼?嘗試閱讀一些理論之前盲目運行片段。您需要將您的訪問權限同步到此列表中,以避免不可預知的行爲 – Dimezis

回答

2

你正在數據結構的併發修改ArrayList

  1. ArrayList的不是線程安全的。
  2. 你的循環迭代不是原子的。
  3. 您不提供任何東西訂購。

如果我們將其命名A1 - 加入線程1,A2 - 加入線程2,R1 - 刪除INT線程1,R2 - 在線程2和>移除之前。 在一個迭代,你可以得到:

  1. A1 > R1 > A2 > R2
  2. A1 > A2 > R1 > R2
  3. A1 > A2 > R2 > R1
  4. ...

我們只知道,總是A1 > R1A2 > R2 另外一個調度會執行若干只有在切換到線程2後纔會在線程1中進行迭代。

因此,沒有理由期望在您的兩個線程中進行任何操作排序。在這種情況下你所擁有的只是在單個線程中添加和刪除的編程順序。但是你沒有一個關聯之前發生。詳見JLS 17。 但是最好先有一個基本的理解。

+0

我想,它比我想象的要多得多。現在我將抽象出它,接受原因爲「沒有使用線程安全方法」。當我足夠學習時,我將閱讀JLS並準確找出爲什麼發生的一切。但謝謝你的答案! spasibo – Sam

+0

當你仔細檢查你所引用的文件是什麼時候......那麼這兩個陳述**會在訂單發生前發生。請參閱http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5 – GhostCat

+0

@GhostCat沒有順序:線程A中的'add' HBH'add'在線程B中。你在說什麼? –

0

如果你想同時使用清單,你必須選擇一個線程安全的實現,如java.util.Vector

public static final List<String> notes = new Vector<String>(); 

或者您可以使用java.util.concurrent.CopyOnWriteArrayList

+1

對於所有意圖和目的,應該考慮棄用「Vector」,並且有更好的併發集合使用比這(因爲'矢量的同步是相當原始的,容易出錯)。 –