2013-08-31 59 views
1

我讀過很多文章認爲System.out.println()使代碼有些線程安全,所以模擬比賽System.out.println()應該從代碼中刪除。易變與Java中的System.out.println()

現在的PrintStreamwrite()方法上this同步寫入流之前,所以每次write()被稱爲然後持有鎖和釋放。 PrintStream的

public void write(int b) { 
    try { 
     synchronized (this) { //acquires a lock 
     ensureOpen(); 
     out.write(b); 
     if ((b == '\n') && autoFlush) 
      out.flush(); 
     } 
    } 
    catch (InterruptedIOException x) { 
     Thread.currentThread().interrupt(); 
    } 
    catch (IOException x) { 
     trouble = true; 
    } 
    } 

write()方法會影響比賽的行爲?

假設:

boolean flag = true; 

Thread 1: 
System.out.println(); 
flag = false; 
System.out.println(); 


Thread 2: 
System.out.println(); 
while(flag){ 
    System.out.println(); 
} 

現在,如果我們看到那麼這兩個線程都鎖定即this(PrintStream的)相同的對象,現在作爲flag包含兩個sysouts,這是獲取和釋放在它們之間鎖,然後flag值將從緩存中刷新並更新到其他線程可以看到它的內存中。

因此,模擬比賽是困難的是有理論上的可能性,這個代碼將是線程安全的,線程2將看到對flag所做的更改?

如果是,那麼可以在volatile同樣的效果可以用System.out.println();實現?

回答

2

的JLS具有以下對話題說:An unlock on a monitor happens-before every subsequent lock on that monitor.17.4.5

您可以在確切定義閱讀起來之前發生,等等。在JLS但是這基本上意味着是每一個讀並寫發生在一個線程它解鎖鎖將通過獲取鎖的另一個線程可以看到之前(小心!如果有一個線程3誰寫的標誌,而不獲取鎖就沒有必要同步)

既然你在這種情況下,同一個對象鎖定,這意味着是的,它保證了更新的flag值將是SE由線程2.

雖然我沒有看到在文檔中提到,它是保證PrintStream獲得一個鎖,並且是線程安全的使用,所以你依靠這裏的實現細節(一這是不太可能被打破的)。

1

線程安全行爲與System.out.println()相關的問題通常會在某人在其代碼中存在間歇性問題時出現,並且當他們添加「調試」時,問題就會消失。高速緩存相干店出現這種情況與println的的同步性,使得臭蟲「隱形」 ....但....

...它不會使錯誤消失,它只是使得它非常非常少可能會發生,並且出於實際的目的,該錯誤消失了。但是,您刪除了println,並且錯誤又回來了。

在你的代碼示例,你有兩個線程用布爾。當我看了你的代碼,我解釋了期望的行爲(是線程安全的)將在以下主題2:

Thread 2: 
System.out.println(); 
while(flag){ 
    System.out.println(); // PL2 - PrintLine2 
} 

代碼的意圖是,第二的println應該只發生在國旗是真的。

換句話說,在循環中,如果線是(添加的標誌值對的println):

System.out.println(flag); // PL2 - PrintLine2 

然後,將印刷結果應始終爲「真」 - 從未假。

這似乎是這樣,但這只是因爲這段代碼是平凡的....如果有任何顯著的工作「中的println前發生的事情的話,那就更明顯.....例如,考慮以下幾點:

public class ThreadPrintln { 

    private static boolean flag = true; 

    private static class ThreadTwo implements Runnable { 
     @Override 
     public void run() { 

      long sum = 1L; 

      System.out.println("Thread2Start"); 
      while (flag) { 
       // non-trivial code 
       for (int i = 0; i < 100000; i++) { 
        sum += i; 
       } 
       System.out.println(flag); // PL2 - PrintLine2 
      } 
      System.out.println("Sum is " + sum); 

     } 
    } 

    public static void main(String[] args) throws InterruptedException { 
     Thread t = new Thread(new ThreadTwo()); 
     t.start(); 
     System.out.println("Main About to wait"); 
     Thread.sleep(1000); 
     System.out.println("Main about to set"); 
     flag = false; 
     System.out.println("Main about to join"); 
     t.join(); 
     System.out.println("Main about to exit"); 
    } 
} 

當我在我的筆記本上運行此我得到的結果是:

true 
true 
true 
.... 
.... many true values 
.... 
true 
true 
true 
Main about to set 
Main about to join 
false 
Sum is 38724612750001 
Main about to exit 

**注意它打印「假」在倒數第二行! **

使用println()不會使邏輯線程安全!

作爲「不平凡」的代碼變得越來越少,它減少了你會得到不良行爲的可能性,但並不能完全消除。刪除了「不平凡」的代碼並沒有消除機會,如果線程錯誤的行爲,只會使得它這麼少,你是不可能看到它在測試時它.....但是,在某些時候它胡作非爲。

+0

線程安全是不一樣的「鎖定」。有很多算法在那裏能見度是你所需要的。如你的例子所示,其他線程確實看到了更新後的標誌值(如果沒有println,那麼獲得無限循環是完全可以接受的)。給定的可見性保證是否足夠取決於算法。 – Voo