2016-12-26 95 views
0

因此,我正在研究java併發性,試圖創建不好的併發示例,觀察它們失敗並修復它們。關於java併發結果

但代碼似乎從未打破......我在這裏錯過了什麼?

我有一個「共享對象」,是我的HotelWithMaximum實例。據我所知,這個類不是線程安全的:

package playground.concurrent; 

import java.util.ArrayList; 
import java.util.List; 

public class HotelWithMaximum { 

    private static final int MAXIMUM = 20; 

    private List<String> visitors = new ArrayList<String>(); 

    public void register(IsVisitor visitor) { 
     System.out.println("Registering : " + visitor.getId()); 
     System.out.println("Amount of visitors atm: " + visitors.size()); 

     if(visitors.size() < MAXIMUM) { 
      //At some point, I do expect a thread to be interfering here where the condition is actually evaluated to 
      //true, but some other thread interfered, adds another visitor, causing the previous thread to go over the limit 
      System.out.println("REGISTERING ---------------------------------------------------------------------"); 
      //The interference might also happen here i guess... 
      visitors.add(visitor.getId()); 
     } 
     else{ 
      System.out.println("We cant register anymore, we have reached our limit! " + visitors.size()); 
     } 
    } 

    public int getAmountOfRegisteredVisitors() { 
     return visitors.size(); 
    } 

    public void printVisitors() { 
     for(String visitor: visitors) { 
      System.out.println(visitors.indexOf(visitor) + " - " + visitor); 
     } 
    } 
} 

遊客是「的Runnable」(它們實現從Runnable接口擴展我接口IsVisitor),他們都是這樣實現的:

package playground.concurrent.runnables; 

import playground.concurrent.HotelWithMaximum; 
import playground.concurrent.IsVisitor; 

public class MaxHotelVisitor implements IsVisitor{ 

    private final String id; 
    private final HotelWithMaximum hotel; 

    public MaxHotelVisitor(String id, HotelWithMaximum hotel) { 
     this.hotel = hotel; 
     this.id = id; 
    } 

    public void run() { 
     System.out.println(String.format("My name is %s and I am trying to register...", id)); 
     hotel.register(this); 
    } 

    public String getId() { 
     return this.id; 
    } 

} 

然後,使這一切運行在一個例子,我在不同的類下面的代碼:

public static void executeMaxHotelExample() { 
     ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(6); 
     HotelWithMaximum hotel = new HotelWithMaximum(); 

     for(int i = 0; i<100; i++) { 
      executor.execute(new MaxHotelVisitor("MaxHotelVisitor-" + i, hotel)); 
     } 

     executor.shutdown(); 

     try{ 
      boolean finished = executor.awaitTermination(30, TimeUnit.SECONDS); 

      if(finished) { 
       System.out.println("FINISHED WITH THE MAX HOTEL VISITORS EXAMPLE"); 
       hotel.printVisitors(); 
      } 
     } 
     catch(InterruptedException ie) { 
      System.out.println("Something interrupted me...."); 
     } 
    } 

    public static void main(String[] args) { 
     executeMaxHotelExample(); 
    } 

現在,我缺少什麼?爲什麼這似乎永遠不會失敗?酒店類不是線程安全的,對嗎?在這個例子中唯一使線程安全的線程是安全的(因爲沒有其他代碼會在酒店類中使用線程不安全列表),我應該使註冊方法「同步」,對吧? 的結果「printVisitors()」中的主要方法方法,始終是這樣的:

FINISHED WITH THE MAX HOTEL VISITORS EXAMPLE 
0 - MaxHotelVisitor-0 
1 - MaxHotelVisitor-6 
2 - MaxHotelVisitor-7 
3 - MaxHotelVisitor-8 
4 - MaxHotelVisitor-9 
5 - MaxHotelVisitor-10 
6 - MaxHotelVisitor-11 
7 - MaxHotelVisitor-12 
8 - MaxHotelVisitor-13 
9 - MaxHotelVisitor-14 
10 - MaxHotelVisitor-15 
11 - MaxHotelVisitor-16 
12 - MaxHotelVisitor-17 
13 - MaxHotelVisitor-18 
14 - MaxHotelVisitor-19 
15 - MaxHotelVisitor-20 
16 - MaxHotelVisitor-21 
17 - MaxHotelVisitor-22 
18 - MaxHotelVisitor-23 
19 - MaxHotelVisitor-24 

有nevere更多然後在列表中20人......我覺得很奇怪......

+0

首先,小樣本大小可能不太可能表現出併發性問題。也許運行同樣的expoument一百萬次?其次,您可能希望在打印到sysout'REGISTERING'的行之後添加一個'Thread.sleep(x)'或'Thread。yield()'試圖鼓勵這個問題出現在這一點上。 – vikingsteve

+0

嗯,這可能確實是一種可能性,我會研究這一點。我有一些令人不安的結果與另一個測試相當快,雖然... –

回答

1

ThreadPoolExecutorjava.util.concurrent

java.util.concurrent包的Java併發工具框架是包含用於在Java應用程序處理併發線程安全類型庫

所以ThreadPoolExecutor走的是syncorinous處理的護理

請注意:ThreadPoolExecutor使用BlockingQueue管理其作業隊列 java.util.concurrent.BlockingQueue是要求所有的實現是線程安全的接口。

根據我的理解,java.util.concurrent的主要目標之一是,您可以在很大程度上運行而無需使用難以使用的java的低級併發基元synchronized, volatile, wait(), notify(), and notifyAll()

還要注意的是ThreadPoolExecutor實現ExecutorService這並不能保證所有的實現是線程安全的,但根據文檔 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html

操作在一個線程之前提交一個Runnable或贖回的任務到一個ExecutorService 發生在之前發生的任何動作,這反過來發生 - 在通過Future.get()檢索結果之前。

happen-before說明:

的Java™語言規範定義上存儲器操作之前發生關係如讀取和共享變量的寫入。由一個線程寫入的結果保證對另一個線程的讀取可見,如果發生寫入操作,則在讀取操作之前,只讀取

換句話說 - 通常不是線程安全的。 BUT

的java.util.concurrent和所有類的方法及其子包這些保證擴展到更高級同步

+0

嗯,我真的很努力與文件的報價。我從中得到的是執行者服務以某種方式保證事前合同......但是如何? –

+0

@ ocket-san查看更新的答案 - 我希望這能更好地澄清 – dreamer

+0

感謝您的更新!我會深入調查,因爲我仍然認爲我理解錯了。但我肯定會回到你的答案! –

1

該代碼似乎從未打破...我在這裏錯過了什麼?

Java語言規範爲實現者提供了很大的靈活性,可以最有效地使用任何給定的多處理器體系結構。

如果您遵守編寫「安全」多線程代碼的規則,那麼應該保證一個正確實現的JVM將以您期望的方式運行您的程序。但如果你打破的規則,那不是保證你的程序會行事不端。

通過測試發現併發錯誤是一個難題。一個非「線程安全」程序可能在一個平臺(即架構/操作系統/ JVM組合)上100%的時間內工作,它可能在其他某個平臺上總是失敗,並且它在某個第三平臺上的性能可能取決於其他平臺進程正在運行,在一天的時間內,或者只能在其他變量上運行。

+0

感謝您的回答。所以基本上,我知道它不是線程安全的,但通過做測試很難證明它......昨天晚上我開始閱讀Brian Goetz和其他人的「Java Concurrency in Practice」,他們基本上在這本書的開始。它是一個恥辱,但我喜歡證明知識:-) –

+0

@ ocket-san,也許你會成爲一名計算機科學家......你可以假設,沒有兩個內存操作在同一時間發生,當你推理多線程代碼:因此,多線程程序的每個可能的執行都等同於以某種順序執行所有內存操作的單個線程(稱爲_serialization_)。你正在尋找的是一種技術來證明某些程序的所有可能的序列化是否導致正確的結果。但是,大多數真實程序的序列化數量是天文數字。祝你好運! –

+0

:-)感謝您的見解。我只會接受這樣一個事實,即併發編程主要是「按規則編程」,並且很難說服那些不瞭解規則的人說他們寫的是不正確的。我還應該更多地研究規則。再次感謝! –

1

你說得對。

當您同時使用更多的執行程序時,您可以重現併發性問題,如Executors.newFixedThreadPool(100);而不是6.然後,更多的線程將同時嘗試它,並且概率更高。由於競賽狀態/溢出只能發生一次,因此您必須多運行一次主隊才能獲得更多訪問者。

此外,您需要在您期待「干擾」的兩個地方添加Thread.yield(),以使其更容易發生。如果執行非常短/快,則不會有任務切換,並且執行將是原子的(但不能保證)。

您也可以使用ThreadWeaver編寫代碼,該代碼對字節代碼進行操作(增加收益),以使這些問題更可能發生。

這兩個變化,我不時在酒店得到30個和更多的遊客。我有2x2 CPU。

+0

非常感謝您的回答!你讓我興奮,試試看。當我擁有時,我會和你一樣得到時髦的結果,除了你的愛人! –