從 上面的代碼能夠看出,只要在void和public之間加上synchronized關鍵字,就可使run方法同步,也就是說,對於同一個Java類的 對象實例,run方法同時只能被一個線程調用,並當前的run執行完後,才能被其餘的線程調用。即便當前線程執行到了run方法中的yield方法,也只 是暫停了一下。因爲其餘線程沒法執行run方法,所以,最終仍是會由當前的線程來繼續執行。先看看下面的代碼:
sychronized關鍵字只和一個對象實例綁定設計模式
在Test類中的method方法是同步的。但上面的代碼創建了兩個Test類的實例,所以,test1和test2的method方法是分別執行的。要想讓method同步,必須在創建Sync類的實例時向它的構造方法中傳入同一個Test類的實例,以下面的代碼所示:
Sync sync1 = new Sync(test1);
不只可使用synchronized來同步非靜態方法,也可使用synchronized來同步靜態方法。如能夠按以下方式來定義method方法:安全
創建Test類的對象實例以下:
Test test = new Test();
對於靜態方法來講,只要加上了synchronized關鍵字,這個方法就是同步的,不管是使用test.method(),仍是使用Test.method()來調用method方法,method都是同步的,並不存在非靜態方法的多個實例的問題。
在23種設計模式中的單件(Singleton)模式若是按傳統的方法設計,也是線程不安全的,下面的代碼是一個線程不安全的單件模式。性能
// 線程安全的Singleton模式
class Singleton
{
private static Singleton sample;this
private Singleton()
{
}
public static Singleton getInstance()
{
if (sample == null)
{
Thread.yield(); // 爲了放大Singleton模式的線程不安全性
sample = new Singleton();
}
return sample;
}
}
public class MyThread extends Thread
{
public void run()
{
Singleton singleton = Singleton.getInstance();
System.out.println(singleton.hashCode());
}
public static void main(String[] args)
{
Thread threads[] = new Thread[5];
for (int i = 0; i < threads.length; i++)
threads[i] = new MyThread();
for (int i = 0; i < threads.length; i++)
threads[i].start();
}
}spa
在上面的代碼調用yield方法是爲了使單件模式的線程不安全性表現出來,若是將這行去掉,上面的實現仍然是線程不安全的,只是出現的可能性小得多。
程序的運行結果以下:線程
上 面的運行結果可能在不一樣的運行環境上有全部同,但通常這五行輸出不會徹底相同。從這個輸出結果能夠看出,經過getInstance方法獲得的對象實例是 五個,而不是咱們指望的一個。這是由於當一個線程執行了Thread.yield()後,就將CPU資源交給了另一個線程。因爲在線程之間切換時並未執 行到建立Singleton對象實例的語句,所以,這幾個線程都經過了if判斷,因此,就會產生了創建五個對象實例的狀況(可能建立的是四個或三個對象實 例,這取決於有多少個線程在建立Singleton對象以前經過了if判斷,每次運行時可能結果會不同)。
要想使上面的單件模式變成線程安全的,只要爲getInstance加上synchronized關鍵字便可。代碼以下:
public static synchronized Singleton getInstance() { }
固然,還有更簡單的方法,就是在定義Singleton變量時就創建Singleton對象,代碼以下:
private static final Singleton sample = new Singleton();
而後在getInstance方法中直接將sample返回便可。這種方式雖然簡單,但不知在getInstance方法中建立Singleton對象靈活。讀者能夠根據具體的需求選擇使用不一樣的方法來實現單件模式。設計
在使用synchronized關鍵字時有如下四點須要注意:code
1. synchronized關鍵字不能繼承。
雖然可使用synchronized來定義方法,但synchronized並不屬於 方法定義的一部分,所以,synchronized關鍵字不能被繼承。若是在父類中的某個方法使用了synchronized關鍵字,而在子類中覆蓋了這 個方法,在子類中的這個方法默認狀況下並非同步的,而必須顯式地在子類的這個方法中加上synchronized關鍵字才能夠。固然,還能夠在子類方法 中調用父類中相應的方法,這樣雖然子類中的方法不是同步的,但子類調用了父類的同步方法,所以,子類的方法也就至關於同步了。這兩種方式的例子代碼以下:
在子類方法中加上synchronized關鍵字對象
在子類方法中調用父類的同步方法繼承
2. 在定義接口方法時不能使用synchronized關鍵字。
3. 構造方法不能使用synchronized關鍵字,但可使用下節要討論的synchronized塊來進行同步。
4. synchronized能夠自由放置。
在 前面的例子中使用都是將synchronized關鍵字放在方法的返回類型前面。但這並非synchronized可放置惟一位置。在非靜態方法 中,synchronized還能夠放在方法定義的最前面,在靜態方法中,synchronized能夠放在static的前面,代碼以下:
但要注意,synchronized不能放在方法返回類型的後面,以下面的代碼是錯誤的:
synchronized關鍵字只能用來同步方法,不能用來同步類變量,以下面的代碼也是錯誤的。
雖然使用synchronized關鍵字同步方法是最安全的同步方式,但大量使用synchronized關鍵字會形成沒必要要的資源消耗以及性能損 失。雖然從表面上看synchronized鎖定的是一個方法,但實際上synchronized鎖定的是一個類。也就是說,若是在非靜態方法 method1和method2定義時都使用了synchronized,在method1未執行完以前,method2是不能執行的。靜態方法和非靜態 方法的狀況相似。但靜態和非靜態方法不會互相影響。看看以下的代碼:
public class MyThread1 extends Thread
{
public String methodName;
public static void method(String s)
{
System.out.println(s);
while (true)
}
public synchronized void method1()
{
method("非靜態的method1方法");
}
public synchronized void method2()
{
method("非靜態的method2方法");
}
public static synchronized void method3()
{
method("靜態的method3方法");
}
public static synchronized void method4()
{
method("靜態的method4方法");
}
public void run()
{
try
{
getClass().getMethod(methodName).invoke(this);
}
catch (Exception e)
{
}
}
public static void main(String[] args) throws Exception
{
MyThread1 myThread1 = new MyThread1();
for (int i = 1; i <= 4; i++)
{
myThread1.methodName = "method" + String.valueOf(i);
new Thread(myThread1).start();
sleep(100);
}
}
}
運行結果以下:
從上面的運行結果能夠看出,method2和method4在method1和method3未結束以前不能運行。所以,咱們能夠得出一個結論,如 果在類中使用synchronized關鍵字來定義非靜態方法,那將影響這個中的全部使用synchronized關鍵字定義的非靜態方法。若是定義的是 靜態方法,那麼將影響類中全部使用synchronized關鍵字定義的靜態方法。這有點象數據表中的表鎖,當修改一條記錄時,系統就將整個表都鎖住了, 所以,大量使用這種同步方式會使程序的性能大幅度降低。