1

我現在正在學習併發性,並且我試圖編寫一個應用程序,它應該演示使用併發收集時發生以前發生的關係。 如java.concurrent包指出:併發收集發生之前的關係

的java.util.concurrent中和所有類的方法及其子包 這些保證擴展到更高級別的同步。 特別是:在將對象放置到任何 併發收集之前的線程中的動作發生在訪問 之後的動作之前或從另一個線程中的集合中移除該元素。

我寫的下一類:

import java.util.Deque; 
import java.util.concurrent.LinkedBlockingDeque; 
import java.util.concurrent.atomic.AtomicBoolean; 

public class HappensBefore { 
    static int  checks  = 0; 
    static int  shouldNotHappen = 0; 

    static Deque<Integer> syncList = new LinkedBlockingDeque<>(); 
    public static boolean varToHappenBefore = false; //this var must be false when new element added to collection 

    static AtomicBoolean flag  = new AtomicBoolean(); 

    static class SyncTask implements Runnable { 

    int  localTemp = -1; 
    private final Thread t = new Thread(new Counter()); 

    @Override 
    public void run() { 
     t.start(); 
     while (syncList.isEmpty()) { //just skip 
     } 

     while (true) { 
     if (!Thread.interrupted()) { 
      if (flag.get()) { 
      int r = syncList.peekLast(); 
      if (r != localTemp) { 
       if (varToHappenBefore) { 
       shouldNotHappen++; 
       } 
       varToHappenBefore = true; 
       localTemp = r; 
       checks++; 
      } 
      flag.set(false); 
      } 
     } else { 
      t.interrupt(); 
      break; 
     } 
     } 
    } 
    } 

    static class Counter implements Runnable { 
    int ctr = 0; 

    @Override 
    public void run() { 
     while (!Thread.interrupted()) { 

     if (!flag.get()) { 
      flag.set(true); 
      varToHappenBefore = false; 
      syncList.add(ctr++); 
     } 
     } 
    } 
    } 


    public static void main(String[] args) throws InterruptedException { 
    SyncTask st = new SyncTask(); 
    Thread s = new Thread(st); 
    s.start(); 
    Thread.sleep(10000);//runtime ms 

    s.interrupt(); 
    // s1.interrupt(); 
    System.out.println("Elems times added: " + checks); 
    System.out 
     .println("Happens-before violated times: " + shouldNotHappen); 
    } 
} 

我做什麼推出線程1,這lauches線程2。 線程1檢查公共布爾值varToHappenBefore最初設置爲false。當thread1從集合中保存新的elem時,它將此布爾值設置爲true。在下一個新元素。如果此布爾值仍然爲真,則會發生 - 違規發生之前,shouldNotHappen將遞增。

線程1檢查併發收集是否有新元素,如果是,則將其保存在臨時變量var中,然後遞增總計數器檢查。 然後它切換爲讓thread2添加新元素的原子布爾值。在添加新元素之前,varToHappenBefore設置爲false。由於原子布爾標誌,在thread1執行之前,thread2不運行代碼。但是,在添加元素並檢查varToHappenBefore之前切換thread2中的標誌,因爲這兩個操作(elem add和boolean toggle)是通過原子布爾值同步的,否則。我確保在thread1運行後thread2只運行一次。如果varToHappenBefore發生在添加elem之前。然後在thread1中讀取(thread1僅在從集合中讀取新elem時檢查varToHappenBefore),然後在程序結束後,varToHappenBefore必須保持爲0。 但我得到下一個結果:

elems的時候還說:〜10 000 000

之前發生違反時間:0-10

也許我做了錯事,多線程是微妙而複雜。希望你的幫助。

編輯: 我要逃跑的情況時,線程1 setFlag(false)之後它是真實的,並得到ELEM之前。從收集,因爲thread2然後可以從線程1中獲取元素和設置varToHappenBefore = true;之間工作。如果我使AtomicBoolean.compareAndSet()檢查塊,然後我得到80k失敗每8mil。它是可預測和清晰的。但是當我沒有爲thread2添加額外的元素來添加額外的元素來讀取集合和設置布爾值之間的時候,當布爾值爲true並且出現新元素時,我仍然會得到幾個順序讀取。

+0

對於我來說,真的很難跟隨代碼所要做的事情。我注意到的一件事是你沒有使用['AtomicBoolean.compareAndSet()'](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html #compareAndSet%28boolean,%20boolean%29)用於更新標誌。相反,你使用單獨的'get'和'set'調用,這首先破壞了使用原子布爾值的目的,因爲沒有阻止值在調用之間改變。 –

+0

另外,你有全局的'static'變量(例如'syncList'和'flag'),它們的狀態應該是''synchronized''。僅僅因爲你使用原子布爾值來存儲一個狀態,並不意味着你不需要同步訪問所有相互依賴的變量。 –

+0

好吧,你可能是正確的AtomicBoolean.compareAndSet()。我做了修改,結果是一樣的。 – Natal

回答

1

您的SyncTask清除flag即使它發現列表尚未更改。所以,你可能會錯過列表添加事件。這使得你的假設不正確的:失蹤事件發生後

  1. 下輪比賽,SyncTask認定名單發生變化時,它假定Counter已經完成了它的任務,檢查(全成),並會重置varToHappenBefore。但Counter實際上在之後向列表添加了元素。
  2. 下一輪SyncTask創建列表再次更改,它假定Counter已完成其任務,檢查varToHappenBefore並發現它未被清除。這是因爲Counter尚未清除它。

只需將flag.set(false);轉換爲if (r != localTemp)塊,並且一切都將正常工作。

+0

你是對的!謝謝,這真的很棒。 – Natal

+0

一個注意事項:在第二輪計數器中,我明白將元素添加到列表中不是_after_,而是在**'varToHappenBefore'重新加載之前**,因爲如果它在之後執行,則在SyncTask之前應將varHappensBefore設置爲false下次發現列表發生變化。 – Natal

+0

我的意思是隻有元素添加後會發生,沒有'varToHappenBefore'注意。你是對的,'Counter'應該清除'varToHappenBefore',然後'SyncTask'重新進行輪迴(2)。 – Tsyvarev

0

最清晰的演示會非常簡單。讓線程A創建一些可變類的實例,更新它,並將其加入隊列。然後,讓線程B從隊列中移除實例,並驗證對象的狀態是否符合預期。

2

你的例子有點複雜,分析和修復。但如果你只是這樣做是爲了瞭解之前發生的合同在併發收集的話,那麼試試這個程序 -

public class HappensBeforeTest { 

private static boolean producerStopped; 

private static final Queue<String> queue = new LinkedBlockingQueue<>(); 
//private static final Queue<String> queue = new LinkedList<>(); 

public static void main(String[] args) { 
    new Thread(new Producer()).start(); 
    new Thread(new Consumer()).start(); 
} 

private static class Producer implements Runnable { 

    public void run() { 
     int count = 0; 
     while (count++ < 10) { 
      producerStopped = (count == 10); 
      queue.add(String.valueOf(count)); 
     } 
     System.out.println("Producer finished"); 
    } 
} 

private static class Consumer implements Runnable { 

    public void run() { 
     while (!producerStopped) { 
      queue.poll(); 
     } 
     System.out.println("Consumer finished"); 
    } 
} 

}

queueLinkedBlockingQueue兩個線程ProducerConsumer結束。

但當queueLinkedList有時Consumer沒有完成和保持運行,因爲producerStopped標誌仍清晰可見假爲Consumer即使Producer已更新,以正確的。

+0

是的,你說得對。這工作。簡單而強大。謝謝。 – Natal