在之前的例子中,多線程不做處理很容易出現線程安全問題,解決的方法就是“加鎖”。在Java中有兩種方式進行加鎖,一種是同步代碼塊,一種是同步函數。
同步代碼塊
將需要同步處理的代碼,用synchronized關鍵字進行包裝。比如之前的賣票例子,將if判斷封裝到synchronized關鍵字裏面。這樣,一旦某個線程進入synchronized代碼塊,在該線程退出之前,其它線程都無法進入。
public void sellTicket(){
synchronized(obj){
if(num>0){
try {
Thread.sleep(100);
}catch (InterruptedException e){e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
}
}
}
同步代碼塊的鎖:synchronized關鍵字後面接的類對象就是“鎖”。多線程要保證鎖的一致性,確保使用的是同一個鎖。
同步函數
第二種方式是在需要同步的方法前面加synchronized關鍵字,叫作同步函數。
public synchronized void sellTicket(){
if(num>0){
try {
Thread.sleep(100);
}catch (InterruptedException e){e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
}
}
同步函數的鎖:同步代碼塊的鎖,是關鍵字後面的對象 。那同步函數的鎖是什麼呢?是調用同步函數的對象,一般是this指針。
靜態同步函數的鎖:如果方法是靜態的,那麼就沒有this指針。鎖是什麼呢?鎖就是該類的Class對象,可以通過getClass方法或者.class屬性進行獲取。
單例模式的多線程漏洞
單例模式的實現,有兩種方式,一種稱爲“餓漢式”,一種稱爲“懶漢式”。
//餓漢式
Class Singleton{
private static final Singleton s=new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return s;
}
}
//懶漢式
Class Singleton{
private static final Singleton s=null;
private Singleton(){}
public static Singleton getInstance(){
if(s==null){
s=new Singleton();
}
return s;
}
}
查看有不有多線程漏洞,看①是否有共享數據?這裏共享數據就是單例對象s。②有無多線程操作?在多線程情況下,自然有。
問題:對於餓漢式而言,不存在多線程漏洞。但是對於懶漢式而言,就存在。
假設有兩個線程,線程0先進入if判斷,此時s==null,線程0進入if代碼塊,正要new一個對象的時候,CPU對線程進行切換。此時s仍舊是null,線程1也順利進入if代碼塊,new了一個Singleton對象。CPU再次切到線程0,線程0也new了一個Singleton對象。這時,就不是單例類了。
解決:加鎖不就行了嘛。但是這麼做,效率比較低,無論Singleton對象是否存在,每一次都要判斷鎖。
public static synchronized Singleton getInstance(){
if(s==null){
s=new Singleton();
}
return s;
}
再判斷鎖之前,加一個判斷,如果對象存在,就直接返回對象。如果對象不存在,再判斷鎖。可以減少鎖的判斷,提高效率。
public static Singleton getInstance(){
if(s==null){
synchronized(Singleton.class){
if(s==null){
s=new Singleton();
}
}
}
return s;
}