2016-04-21 35 views
0

假設我們有n個線程可以訪問這個函數,我聽說即使布爾值只是一個點翻轉,這個過程也不是原子的。在這個函數中,opening = true是否需要包裝在一個同步? opening是該類的成員。是否需要設置布爾值的關鍵部分?

boolean opening = false; 

public void open() { 

    synchronized (this) { 
     while (numCarsOnBridge != 0 || opening || closing) { 
      // Wait if cars on bridge, opening or closing 
      try { 
       // Wait until all cars have cleared the bridge, until bridge opening 
       wait(); 
      } catch (InterruptedException e) {} 
     } 
     // By now, no cars will be under the bridge. 
    } 

    if (!opening) { 
     opening = true; // Do we need to wrap this in a synchronize? 
     // notifyAll(); // Maybe need to notify everyone that it is opening 
     try { 
      sleep(60); // pauses the current thread calling this 
     } catch (InterruptedException e) {} 

     synchronized (this) { 
      drawBridgeClosed = false; // drawBridge is opened 
      opening = false; 
      notifyAll(); // Only notify all when state has fully changed 
     } 
    } 

} 

回答

1

是的,對一個布爾值的併發修改需要同步,使用AtomicBoolean或synchronized結構。

而且,你不能沒有在這裏擁有一個監控器調用notifyAll的():

if (!opening) { 
    opening = true; // Do we need to wrap this in a synchronize? 
    // notifyAll(); // Maybe need to notify everyone that it is opening 

所以,你已經有2個原因,包裝在synchronized塊。

1

其實opening = true原子 - 它只是不會產生所謂的內存屏障

您應該製作opening(和closing就此事)volatile

volatile boolean opening = false; 

這每次將迫使存儲器屏障opening的變化,從而確保opening變量的任何cacheing被刷新。

+0

易失性只確保爲先前的讀取和寫入排序。 volatile讀取(進入if條件)不會對後續寫入的順序(在if條件中)產生任何保證,因此,如果我們只讓'opening'易失性,兩個線程仍可能進入相同if條件之前可以重置標誌。 – KookieMonster

+0

@KookieMonster - 你是對的 - 我試圖確保代碼的功能如預期的那樣(即'opening = true'對其他線程可見)。代碼確實還有其他漏洞,特別是圍繞此操作的種族。 – OldCurmudgeon

+0

雖然設置布爾本身本身就是一個原子操作的確是真的,但在這種情況下,注意力放在易變的而不是關鍵的部分上,這是imho的誤導,而這個答案可能會造成混淆。 – kRs

1

臨界區是必要的。爲了解釋這一點,我們需要考慮原子性。具體來說,我們需要對標誌(opening)進行讀寫操作,使其成爲單個原子操作(原子意思是它們在一個不可分割的步驟中發生)。

考慮這個簡化的例子;

if (flag) //read 
{ 
     flag = false; //write 
     foo(); //arbitrary operation 
} 

在這個例子中,讀取操作,然後將寫操作發生之後,任何事情都有可能之間發生(例如,線程#2磨磨蹭蹭之前線程#看到true 1套價值false,在這種情況下foo()會被調用兩次)。

要解決這個問題,我們需要確保讀取和寫入都在一個步驟中進行。我們可以通過將兩者都放在同一個同步塊中來做到這一點;

synchronized(monitor) 
{ 
    if (flag) //read 
    { 
     flag= false; //write 
     foo(); //some arbitrary operation 
    } 
} 

由於同步塊內的所有內容被認爲是一個偉大的大原子操作,讀/寫也是原子和線程安全的實現(這樣foo將只能通過每次單個線程的標誌一度被稱爲設置爲true)。


的AtomicBoolean(如在對方的回答提到的),也可在這種情況下使用的,因爲這類提供的操作來讀取並以相同的步驟寫。例如;

static AtomicBoolean flag = new AtomicBoolean(false); 

public void run() 
{ 
    if (flag.getAndSet(false)) //get and then set the flag 
    { 
     //the flag is now set to 'false' 
     //and we will enter this section if it was originally 'true' 
     foo(); 
    } 
} 

作爲一個方面說明,「getAndSet」操作在功能上與此類似;

public boolean getAndSet(boolean newVal) 
{ 
    synchronized(this) 
    { 
     boolean val = oldVal; 
     oldVal = newVal; 
     return val; 
    } 
} 

然而,它以一種高度優化的方式來實現,所以通常比使用synchronized關鍵字快得多。