2016-12-22 32 views
0

我觀察到使用同步方法或同步塊產生不同結果的場景。 從下面代碼:同步方法和塊的行爲不同

class Callme { 
    void call(String msg) { 
     System.out.print("[" + msg); 
     try { 
      Thread.sleep(1000); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
     System.out.println("]"); 
    }  
} 

class Caller implements Runnable{ 
    String msg; 
    Callme target; 
    Thread t; 

    public Caller(Callme target, String msg) { 
     this.target = target; 
     this.msg = msg; 
     t = new Thread(this, "Caller thread"); 
     t.start(); 
    } 

    @Override 
    public void run() { 
     synchronized(target) { 
      target.call(msg); 
      new Callme().call(msg); 
     } 
    } 

} 

public class Test { 

    public static void main(String[] args) throws InterruptedException { 
     Callme obj = new Callme(); 

     new Caller(obj, "thread1"); 
     new Caller(obj, "thread2"); 
     new Caller(obj, "thread3"); 

     Thread.currentThread().join(); 
    } 
} 

當我使用同步塊在來電::運行方法的輸出中被如下同步:

[thread1] 
[thread1] 
[thread3] 
[thread3] 
[thread2] 
[thread2] 

但是當我使用了的CallMe同步方法: :呼叫的方法,而不是同步塊時,輸出不同步:

[thread1] 
[thread1[thread2] 
] 
[thread3[thread2] 
] 
[thread3] 

我的期望是在輸出不應該在這兩種情況下貝科同步使用我在調用「Callme :: call」方法時使用不同的對象

這讓我質疑我對Synchronized塊概念的理解嗎?

回答

1

一個同步的方法是等同於synchronized(this)嵌段整個方法的長度,但是您的代碼是使用synchronized(target),和targetCallme共享實例。換句話說:被同步的對象是不同的,所以行爲是不一樣的。

在您使用synchronized(target)的情況下,這意味着所有的線程上的Callme所以同一個實例同步它們的行爲是串行:一個線程會認爲Callme實例的顯示器爲Caller.run方法的整個過程,所以在效果線程被一個接一個地執行。

在同步方法的情況下,每個線程在它們自己的Caller實例上同步,所以實際上沒有序列化(除了寫入System.out)。

一些補充說明:

  • 調用Thread.currentThread().join()是一個壞主意,因爲它會等待自己
  • 一般不會產生,並開始在Runnable實施Thread情況下是將要運行通過該線程:您無法訪問該線程。特別是不要在構造函數中這樣做,因爲您正在向Thread發佈一個部分構建的對象,這在該代碼中並不是一個大問題,但可能會在更復雜的應用程序中導致微妙的錯誤。