4
public class ObjectCounter { 
    private static long numOfInstances = 0; 
    public ObjectCounter(){ 
     synchronized(this){ 
     numOfInstances++; 
     } 
    } 
    **public static synchronized long getCount(){ 
     return numOfInstances; 
    }** 
//vs// 
    **public static long getCount(){ 
     return numOfInstances; 
    }** 
} 

如果我將運行幾個線程,其中一些調用靜態函數getCount(),其中一些創建新的實例。我想每次打電話給getCount()當時的實例數。同步(this)只鎖定同步塊還是全部「this」代碼?

  1. 代碼中的兩個選項有區別嗎?
  2. 如果我鎖定「this」不應該意味着我不能調用getCount(),直到構造函數退出同步塊(可以說我是否不在getCount()上同步)。
  3. 如果我在代碼的某個地方做了一個同步塊,它是否只鎖定同步塊或所有的「this」代碼?
  4. 從這裏開始編輯:謝謝大家,這是非常有幫助的,但我有幾個問題,你的答案。
  5. 如果我理解正確,synchronized(this)塊不會影響(或連接到)靜態同步函數(以鎖定方式而不是numOfInstances增量)?
  6. 有沒有更好的選項來使增量和getCount()函數線程安全? (比如打開一個靜態對象並且同步(obj)而不是同步(this) - 朋友建議)。
  7. 如果我在ObjectCounter類中有一個f1()方法(非靜態),而一個線程在synchronized(this)中,其他線程可以進入f1()塊(不是同步類還是內部有同步塊)?
  8. 如果我在ObjectCounter中有一個f1()方法(非靜態)和f2()方法(非靜態),在f1()中我已經同步(this)塊。當一個線程在同步(this)塊中時,其他線程是否可以輸入f1()塊(不是同步類或內部有同步塊)? (允許在同一個實例說兩個線程「工作」的)

`

+0

您絕不允許調用實例方法(例如,在該實例的構造函數返回之前,任何對象的'getCount()')。唯一可能的方式是如果構造函數將'this'發佈到另一個線程。 (也被稱爲「從構造函數泄漏_this_」)。如果你認爲你必須在構造函數中寫入'synchronized(this)',那麼你可能犯了一個巨大的錯誤。 –

+4

請在您對問題得到正確和完整的答案後,不要編輯您的問題以添加更多問題(或者更糟糕的是,替換您現有的問題)。爲此提出一個新問題,或對後續問題的正確答案發表評論。 – Buurman

+0

所有的答案都非常好,很有幫助,但是我只能選擇一個幫助我的人,而且我認爲只選擇一個並不公平。如果這是必須的,我會選擇一個。 –

回答

7

使用​​意味着爲了一個線程來執行塊或方法,它具有獲取引用的鎖(明確或隱含地)通過該塊或方法。對於static synchronized方法,該鎖是類對象上的監視器。對於synchronized(this)塊,使用的鎖是當前實例上的監視器。在多個方法或塊之間共享鎖是強制更新的原子性和內存可見性的原因,共享鎖也提供可以通過其進行等待和通知的共享通信路徑。

由於靜態同步塊使用與構造函數中塊所使用的不同的鎖,所以輸入靜態同步塊不會被另一個線程訪問需要獲取當前實例上的鎖的塊所阻止,並且同步塊的構造函數對任何東西都沒有影響,鎖的獲取將永遠是無人蔘與的。更重要的是,在這裏,構造函數中的一個線程所做的更改可能不會被其他使用getter的線程看到。同步影響鎖定和內存可見性。

這個更改後的版本將工作:

public class ObjectCounter { 
    private static long numOfInstances = 0; 
    public ObjectCounter(){ 
     synchronized(ObjectCounter.class){ 
      numOfInstances++; 
     } 
    } 
    public static synchronized long getCount(){ 
     return numOfInstances; 
    } 
} 

因爲getter和遞增塊都使用相同的鎖。使不同的線程獲取相同的監視器可確保對計數器的更改安全地發佈,以便訪問該getter的另一個線程可以看到更新後的值。

synchronized關鍵字說:「你必須獲得一個鎖,然後才能進入」,其中爲方法假定鎖:方法的靜態關鍵字是類的監視器,沒有靜態關鍵字它是當前實例上的監視器。要使鎖定正常工作,不同的塊和方法需要使用相同的鎖。可以說,太多的語法糖和太多的東西使得Java的設計變得很方便:允許隱式選擇鎖並將監視器放在java.lang.Object上可能會導致混淆。

WRT你的問題#6:爲了你在這裏做什麼,你最好用AtomicLong。使用同步塊來協調多個需要進行的更改,而不受其他線程的干擾。問題#3,#7和#8看起來非常相似:如果一個方法/塊沒有試圖獲得一個鎖,那什麼都不會阻止線程執行該方法/塊。該對象作爲一個整體沒有得到任何保護,使用同步方法或塊來強制鎖定是什麼保護。根據「使用​​關鍵字」以及更多關於鎖定線程需要獲取的內容來考慮更少。

+2

壞狗! Noobs需要明白,鎖不與塊關聯,不與方法關聯,並且不與變量關聯。我已經失去了看到他們問了幾次的計數,「兩個線程怎麼可能同時進入同一個同步塊/方法?」或者,「兩個線程同時在'count'上同步怎麼可能?」他們需要清楚地說明'synchronized'唯一可以防止的是兩個線程同時鎖定相同的_instance_。 –

+0

@james:我重新試着讓這個更清楚。歡迎您提出改進或修改建議。順便說一句,我的gravatar是一隻貓,雖然是一個粗糙的渲染。 –

+0

哦,親愛的!我一直認爲這是某種雪納瑞。對不起這樣的言論,但是這是你說到的「與一個街區相關的鎖」的部分讓我走了。我有時候可能會對同齡人說同樣的話,但我的同事們都知道,鎖與代碼之間的關聯是我的程序設計方式的一個方面,而不是因爲語言如何工作。 –

-2

代碼中的兩個選項有區別嗎?

是的,有明顯的區別。首先,您正在同步ObjectCounter的類對象上的getCount()方法的線程訪問權限。而在第二你不是。

如果我鎖定「這個」不應該就意味着我不能調用getCount將(),直到 承包商退出synchronized塊(如果我不寫上getCount將 同步讓說() )。

由於存在的對象的僅一個鎖(類鎖是不同,它們通過使用具有​​沿static關鍵字持有),因此,如果一些其它線程正在獲取該鎖由於synchronized(this){或因此synchronized long getCount(){任那麼試圖獲取鎖的新線程必須等到上一個線程釋放鎖。

現在因爲你的情況,你在做,static synchronized long getCount(){,所以,它的鎖定變得與synchronized(this){不同。這意味着如果某個線程因爲synchronized(this){而正在獲取一個鎖,並且某個其他線程試圖調用getCount(),那麼該線程將不會被阻止。

,如果我在代碼做一些地方synchronized塊,它鎖定 唯一的synchronized塊或所有的「本」的代碼?

  1. 非靜態同步: 如果你在代碼中有一些地方synchronized塊,它是非靜態的public synchronized long getCount(){,然後還你的對象的鎖將舉行,所以新的線程試圖獲得鎖必須等到前一個線程釋放鎖。

  2. 靜態同步: 如果你在代碼中有一些地方synchronized塊,它是靜態的public static synchronized long getCount(){,那麼它將對非靜態sychronization的鎖沒有影響。


底線:

  • 只和一個鎖,用於只有一個對象,並且如果該鎖被一些線程獲得然後其他線程必須等待,直到該鎖釋放。

  • 然後有一個類鎖定,如果static關鍵字與​​關鍵字一起使用,則保留該類別鎖定。

+0

你能解釋一下投票嗎? – hagrawal

+0

沒有任何解釋的投票是很差的精神展示。 – hagrawal

+0

什麼新聞?我沒有降低你的答案。 –

1
  1. 是的,有在選項的差異。在上面的選項中,兩個線程不能同時調用getCount(),他們可以在下面的一個線程中調用。

  2. 是的,這是正確的。只能有一個線程同時持有一個對象的鎖。

  3. 每個對象都有自己的鎖。所以它鎖定該對象的所有synchronized (this)塊。

但是請注意,每個對象都有一個自己的鎖,而且每個類都有一個自己的鎖。在構造函數中使用對象鎖來訪問靜態(類)變量,而在getCount()中使用類鎖。這意味着你的代碼不是線程安全的!

0

​​步驟:

  1. 檢查對象的鎖已經被收購。如果是這樣,進入同步塊/方法
  2. 嘗試鎖定。如果鎖已經被另一個線程獲取,則線程將等待鎖釋放,此時它將再次經過週期(2.)