2014-10-07 78 views
5
public class MyStack2 { 
    private int[] values = new int[10]; 
    private int index = 0; 

    public synchronized void push(int x) { 
     if (index <= 9) { 
      values[index] = x; 
      Thread.yield(); 
      index++; 
     } 
    } 

    public synchronized int pop() { 
     if (index > 0) { 
      index--; 
      return values[index]; 
     } else { 
      return -1; 
     } 
    } 

    public synchronized String toString() { 
     String reply = ""; 
     for (int i = 0; i < values.length; i++) { 
      reply += values[i] + " "; 
     } 
     return reply; 
    } 
} 

public class Pusher extends Thread { 
    private MyStack2 stack; 

    public Pusher(MyStack2 stack) { 
     this.stack = stack; 
    } 

    public void run() { 
     for (int i = 1; i <= 5; i++) { 
      stack.push(i); 
     } 
    } 
} 

public class Test { 
    public static void main(String args[]) { 
     MyStack2 stack = new MyStack2(); 
     Pusher one = new Pusher(stack); 
     Pusher two = new Pusher(stack); 
     one.start(); 
     two.start(); 
     try { 
      one.join(); 
      two.join(); 
     } catch (InterruptedException e) { 
     } 
     System.out.println(stack.toString()); 
    } 
} 

MyStack2由於類的方法在鎖是同步的,我所期待的輸出作爲 1 2 3 4 5 1 2 3 4 5但輸出不定。通常它給出:1 1 2 2 3 3 4 4 5 5線程同步 - 什麼時候一個線程釋放物體

根據我的理解,當線程1開始時,它獲取push方法的鎖。內線push()線程一段時間產生。但是當調用yield()時它釋放鎖嗎?現在當線程2啓動時,線程2完成執行之前線程2是否會獲得一個鎖?有人能解釋一下線程釋放堆棧對象上的鎖嗎?

+0

看看這個http://stackoverflow.com/questions/18635616/yield-inside-synchronized-block-lock-release-after-calling-yield – SimY4 2014-10-07 13:57:45

回答

5

A​​方法只會阻止其他線程在執行時執行它。只要它返回其他線程可以(並且通常會立即)獲得訪問權限。

的情況下讓你的1 1 2 2 ...可能是:

  1. 線程1調用push(1)並且被允許在
  2. 線程2調用push(1)同時線程1在使用它被阻止。
  3. 線程1退出push(1)
  4. 線程2獲得訪問push並推1但同時線程1調用push(2)

結果1 1 2 - 您可以清楚地看到它如何繼續。

+0

是的,但如果是這樣的話,程序的輸出不應該是這樣的:1 1 2 2 3 3 4 4 5 5.但通常這是輸出。 – sam 2014-10-07 14:13:47

+7

@javaTech - 沒有合同說Java線程應該公平。幾乎任何順序都可能是你的代碼的結果。 1 1 2 2 ...非常有效。 「 – OldCurmudgeon 2014-10-07 14:20:22

1

看起來你可能對於synchronized和yield關鍵詞的含義有些混淆。

同步意味着一次只有一個線程可以輸入該代碼塊。把它想象成一個大門,你需要一個鑰匙才能通過。它進入的每個線程都只有一個密鑰,並在完成後返回。這允許下一個線程獲取密鑰並執行其中的代碼。無論它們在同步方法中多久都沒關係,一次只能有一個線程進入。

產量向編譯器提出建議(並且是唯一的建議),即當前線程可以放棄其分配的時間,並且另一個線程可以開始執行。然而,這並不總是以這種方式發生。

在你的代碼中,即使當前線程向編譯器提示它可以放棄它的執行時間,它仍然保持着同步方法的關鍵,因此新線程無法進入。

不可預知的行爲來自收益率不會像你預測的那樣放棄執行時間。

希望有幫助!

+0

」同步意味着一次只有一個線程可以進入該代碼塊。「只有當代碼每次進入塊時都總是試圖鎖定_same object_對象。如果foo不是常量,多個線程可以輸入相同的'synchronized(foo){...}'塊。 – 2014-10-07 18:48:10

+0

「收益表明......編譯器......」不是編譯器。編譯器不知道Thread.yield()和其他方法調用之間的區別。神奇(如果有的話)在運行時發生,很可能是JVM實現中的本地線程進行某種「yield」系統調用時。 – 2014-10-07 21:44:56

+2

「不可預知的行爲來自收益不放棄......」我不這麼認爲,正如你所指出的那樣,yield()調用發生在同步塊內部。無論它是否執行任何操作,其他線程都不會在yield()返回之前運行。不可預測性來自於有兩個線程反覆爭奪相同的鎖。假設線程A有鎖,線程B正在等待鎖。當線程A釋放鎖,然後_immediately_試圖再次鎖定它時,哪個線程獲取鎖?這是什麼是不可預測的。 「 – 2014-10-07 21:48:46

2

當你說:

按我的理解,當一個線程啓動它獲取的push方法的鎖。

這是不完全正確的,因爲鎖不僅僅是推的方法。推送方法使用的鎖定位於MyStack2的實例上,該實例被調用。方法pop和toString使用與push相同的鎖。當一個線程在一個對象上調用這些方法時,它必須等到它可以獲得該鎖。調用推送過程中的線程將阻止另一個線程調用pop。線程正在調用不同的方法來訪問相同的數據結構,對訪問該結構的所有方法使用相同的鎖來防止線程同時訪問數據結構。

一旦線程放棄退出同步方法的鎖,調度程序將決定哪個線程獲得下一個鎖。您的線程正在獲取鎖並讓它們多次出現,每次發佈鎖時,都會對調度程序做出決定。你不能對任何將被選中的假設做出任何假設,它可以是任何假設。來自多個線程的輸出通常是混亂的。

+1

」鎖定可以防止數據結構被同時訪問。「小心!新手可能不明白,它不是保護數據的鎖定。他們需要被告知(有時不止一次),保護數據的原因是每一個更新或使用數據的代碼塊都會鎖定同一個鎖。 – 2014-10-07 18:44:51

+0

@詹姆斯:同意了,重寫了。 – 2014-10-07 18:47:10