2012-01-23 385 views
10

這對我來說不是作業,這是給一些大學的學生的任務。我對這個解決方案不感興趣。這個java類線程安全嗎?

任務是創建一個包含整數的類(Calc)。這兩個方法添加和mul應該添加或乘以這個整數。

兩個線程同時建立。一個線程應該調用c.add(3)十次,另一個線程應該調用c.mul(3)十次(當然在同一個Calc對象上)。

Calc類應確保操作交替進行(add,mul,add,mul,add,mul,..)。

我還沒有和併發相關的問題一起工作 - 甚至更少用Java。我想出了Calc的以下實現:

class Calc{ 

    private int sum = 0; 
    //Is volatile actually needed? Or is bool atomic by default? Or it's read operation, at least. 
    private volatile bool b = true; 

    public void add(int i){ 
     while(!b){} 
     synchronized(this){ 
       sum += i; 
      b = true; 
     } 
    } 

    public void mul(int i){ 
     while(b){} 
     synchronized(this){ 
      sum *= i; 
      b = false; 
     } 
    } 

} 

我想知道我是否在正確的軌道上。 (b)部分肯定有更優雅的方式。 我想聽聽你們的想法。

PS:方法的簽名不得改變。除此之外,我不受限制。

+2

您可以使用[的AtomicBoolean(http://docs.oracle.com/javase/6/docs/api/java/util/:

我個人有一對信號燈的實現這個併發/原子/ AtomicBoolean.html)和[AtomicInteger](http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicInteger.html)。 – CoolBeans

+0

@AviramSegal - 詳細說明:這是一個線程調用mul()連續十次。不是每個調用mul()的十個線程。 – s3rius

+0

線程安全與否,任何以這種方式使用布爾值作爲學生作業的人都應該被激發。字段名稱'b'。太好了,太好了。 –

回答

3

揮發性需要,否則優化器可能會優化環路if(b)while(true){}

但你可以waitnotify

public void add(int i){ 

    synchronized(this){ 
     while(!b){try{wait();}catch(InterruptedException e){}}//swallowing is not recommended log or reset the flag 
      sum += i; 
     b = true; 
     notify(); 
    } 
} 

public void mul(int i){ 
    synchronized(this){ 
     while(b){try{wait();}catch(InterruptedException e){}} 
     sum *= i; 
     b = false; 
     notify(); 
    } 
} 
然而

在這種情況下,(B同步塊內檢查)揮發性是這樣做不需要

+1

我會使用notifyAll,以防萬一需求發生變化,並且您可能會有多個添加和/或乘法線程(在這種情況下,您會遇到死鎖)。 –

+0

您是否有任何參考資料支持關於優化離開不易變的東西的陳述?我理解它更多的是可見性......或者是同一問題的一部分?任何鏈接,以幫助更好地瞭解將不勝感激:) – Toby

+0

@託比檢查[此博客文章](http://mailinator.blogspot.com/2009/06/on-java-visibility.html) –

3

是的,volatile是必要的,這不是因爲從boolean到另一個的分配不是原子的,而是阻止變量的緩存,使得其更新值對正在讀取它的其他線程不可見。如果您關心最終結果,那麼sum應該是volatile

話雖如此,使用waitnotify來創建這種交織效果可能會更優雅。

class Calc{ 

    private int sum = 0; 
    private Object event1 = new Object(); 
    private Object event2 = new Object(); 

    public void initiate() { 
     synchronized(event1){ 
      event1.notify(); 
     } 
    } 

    public void add(int i){ 
     synchronized(event1) { 
      event1.wait(); 
     } 
     sum += i; 
     synchronized(event2){ 
      event2.notify(); 
     } 
    } 

    public void mul(int i){ 
     synchronized(event2) { 
      event2.wait(); 
     } 
     sum *= i; 
     synchronized(event1){ 
      event1.notify(); 
     } 
    } 
} 

然後,在您啓動兩個線程後,請致電initiate以釋放第一個線程。

+0

當然,在Java規範中實際上並沒有任何進展的保證。 /我相信'同步'在這裏是不必要的。 –

+0

+1爲chaching問題。我以前閱讀過它,但完全忘了。 – s3rius

+0

@ s3rius:我用一些代碼用wait和notify做了編輯。對我來說這似乎更清楚。 – Tudor

11

嘗試使用鎖接口:

class Calc { 

    private int sum = 0; 
    final Lock lock = new ReentrantLock(); 
    final Condition addition = lock.newCondition(); 
    final Condition multiplication = lock.newCondition(); 

    public void add(int i){ 

     lock.lock(); 
     try { 
      if(sum != 0) { 
       multiplication.await(); 
      } 
      sum += i; 
      addition.signal(); 

     } 
     finally { 
      lock.unlock(); 
     } 
    } 

    public void mul(int i){ 
     lock.lock(); 
     try { 
      addition.await(); 
      sum *= i; 
      multiplication.signal(); 

     } finally { 
      lock.unlock(); 
     } 
    } 
} 

鎖就像你的synchronized塊。但是如果另一個線程持有lock直到.signal()被調用,這些方法將在.await()處等待。

+0

誰先走? –

+0

如果您修改您的示例以包含兩個條件(以處理操作的交替),我將upvote。 – Perception

+0

嗯,我不確定使用相同的信號變量是否是線程安全的。有一種情況是線程調用add會發出allowAccess信號,然後重新獲取鎖本身並找到它的信號。 – Tudor

10

你所做的是一個繁忙的循環:你正在運行的循環只在變量發生變化時纔會停止。這是一種不好的技術,因爲它使CPU非常繁忙,而不是簡單地讓線程等待直到標誌被改變。

我會使用兩個semaphores:一個用於multiply,另一個用於addadd在添加之前必須獲得addSemaphore,並在完成時向multiplySemaphore發佈許可證,反之亦然。

private Semaphore addSemaphore = new Semaphore(1); 
private Semaphore multiplySemaphore = new Semaphore(0); 

public void add(int i) { 
    try { 
     addSemaphore.acquire(); 
     sum += i; 
     multiplySemaphore.release(); 
    } 
    catch (InterrupedException e) { 
     Thread.currentThread().interrupt(); 
    } 
} 

public void mul(int i) { 
    try { 
     multiplySemaphore.acquire(); 
     sum *= i; 
     addSemaphore.release(); 
    } 
    catch (InterrupedException e) { 
     Thread.currentThread().interrupt(); 
    } 
} 
+0

我認爲ReentrantLock版本稍微容易理解。也就是說,這個關於初始狀態似乎更加清楚,並且可能更具適應性,所以+1你去。 –

0

程序是完全線程安全的:

  1. 布爾標誌設置爲揮發性,所以JVM知道不緩存值,並保持一個線程在同一時間寫訪問。

  2. 兩個關鍵部分鎖定當前對象,這意味着一次只能有一個線程訪問。請注意,如果一個線程位於synchronized塊中,則線程不能位於其他關鍵部分中。

以上將適用於該類的每個實例。例如,如果創建了兩個實例,則線程一次將能夠進入多個關鍵部分,但每個關鍵部分的每個實例僅限於一個線程。那有意義嗎?

+0

但它可以改進。這是個問題。 – Jivings

3

嗯。您的解決方案存在許多問題。首先,揮發性不是原子性要求,而是可見性要求。我不會在這裏進入,但你可以閱讀更多關於Java memory model。 (是的,布爾是原子的,但這裏沒有關係)。此外,如果您只在同步塊內部訪問變量,則它們不必是易失性的。

現在,我認爲這是偶然的,但是你的b變量不是隻在同步塊內部訪問的,它碰巧是不穩定的,所以實際上你的解決方案可以工作,但它既不是慣用的也不是推薦的,因爲你等待b在繁忙的循環內改變。無需任何東西即可燃燒CPU週期(這就是我們所說的旋轉鎖,有時它可能會有用)。

一種慣用的解決方案是這樣的:

class Code { 
    private int sum = 0; 
    private boolean nextAdd = true; 

    public synchronized void add(int i) throws InterruptedException { 
     while(!nextAdd) 
      wait(); 
     sum += i; 
     nextAdd = false; 
     notify(); 
    } 

    public synchronized void mul(int i) throws InterruptedException { 
     while(nextAdd) 
      wait(); 
     sum *= i; 
     nextAdd = true; 
     notify(); 
    } 
} 
+0

問題 - 會在那裏while(!b)wait();如果(!b)等待(); ? – s3rius

+1

@ s3rius是的。您必須始終在while循環中測試一個監視器條件(您等待())的條件。有兩個原因。首先,你不知道爲什麼線程被喚醒(使用notify()或notifyAll())。也許是因爲你測試的條件已經改變了,也許是因爲另一個原因。其次,在某些硬件/操作系統體系結構中,可能存在虛假的線程喚醒,即完全沒有理由的喚醒。我相信,這在大多數架構中都不會發生,但這個習語依然存在:您必須始終在一段時間內測試您等待的狀況。 – pron

+0

澄清它,謝謝。 – s3rius

5

正如其他人所說,在解決方案中的volatile是必需的。此外,您的解決方案會自動等待,這會浪費很多CPU週期。也就是說,就有關的正確性而言,我看不出任何問題。

private final Semaphore semAdd = new Semaphore(1); 
private final Semaphore semMul = new Semaphore(0); 
private int sum = 0; 

public void add(int i) throws InterruptedException { 
    semAdd.acquire(); 
    sum += i; 
    semMul.release(); 
} 

public void mul(int i) throws InterruptedException { 
    semMul.acquire(); 
    sum *= i; 
    semAdd.release(); 
} 
+0

我們有完全一樣的想法! –

+0

@JBNizet:是的,但是你到達這個解決方案的速度要快很多(在決定信號量是最好的解決方案之前,我花了相當多的時間修改其他原語。) – NPE

+0

那麼,發展不是速度競賽。如果是的話,我會失去很多時間。不幸的是,比其他更多的方式,stackoverflow是。 –