一、synchronized 的介紹
synchronized 是 Java語言的關鍵字,當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多只有一個線程執行該段代碼,而這段代碼也被稱為臨界區。
synchronized 有多個叫法,而每個叫法都表明synchronized 的特性:
1、內置鎖(又叫 隱式鎖):synchronized 是內置於JDK中的,底層實現是native;同時,加鎖、解鎖都是JDK自動完成,不需要用戶顯式地控制,非常方便。
2、同步鎖:synchronized 用於同步線程,使線程互斥地訪問某段代碼塊、方法。這就是意味着最多只有一個線程能夠獲得該鎖,當線程A嘗試去獲得線程B持有的內置鎖時,線程A必須等待或者阻塞,知道線程B釋放這個鎖,如果B線程不釋放這個鎖,那么A線程將永遠等待下去。
3、對象鎖:准確來說,是分為對象鎖、類鎖。synchronized 以當前的某個對象為鎖,線程必須通過互斥競爭拿到這把對象鎖,從而才能訪問 臨界區的代碼,訪問結束后,就會釋放鎖,下一個線程才有可能獲取鎖來訪問臨界區(被鎖住的代碼區域)。synchronized鎖 根據鎖的范圍分為 對象鎖 和 類鎖。對象鎖,是以對象實例為鎖,當多個線程共享訪問這個對象實例下的臨界區,只需要競爭這個對象實例便可,不同對象實例下的臨界區是不用互斥訪問;而類鎖,則是以類的class對象為鎖,這個鎖下的臨界區,所有線程都必須互斥訪問,盡管是使用了不同的對象實例;
總的來說,對象鎖的粒度要比類鎖的粒度要細,引起線程競爭鎖的情況比類鎖要少的多,所以盡量別用類鎖,鎖的粒度越少越好。
看下面的例子:
FruitCount fruitCount = new FruitCount();
FruitCount fruitCount_3 = new FruitCount();
//線程1、2 使用了同一個FruitCount對象(fruitCount )
Thread thread_1 = new Thread(new MyRunable(fruitCount));
Thread thread_2 = new Thread(new MyRunable(fruitCount));
//線程3使用了不同的FruitCount對象(fruitCount_3 )
Thread thread_3 = new Thread(new MyRunable(fruitCount_3));
線程1、2將會互斥訪問getAmount( )方法,線程3則獨享getAmount( )方法;線程1、2的getAmount( )方法中的對象鎖是fruitCount ,線程3的則是 fruitCount_3;這便是對象鎖的粒度范圍,不同的對象,鎖是相互隔離的。而對於setData( )方法,三個線程都要互斥訪問訪問它,因為是同一個鎖 -- FruitCount.class類鎖。
class FruitCount{
static int price = 5;
static int num = 10;
public void setData(int price,int num){
//類鎖,以FruitCount.class為鎖
synchronized(FruitCount.class){
this.price = price;
this.num = num;
}
}
public int getAmount(){
//對象鎖,以當前對象為鎖
synchronized (this) {
int amount = price*num;
return amount;
}
}
}
class MyRunable implements Runnable{
FruitCount fruitCount;
public MyRunable(FruitCount fruitCount){
this.fruitCount = fruitCount;
}
@Override
public void run() {
//setData方法 有類鎖
fruitCount.setData(5, 10);
//getAmount方法 里面有對象鎖,就是fruitCount對象
fruitCount.getAmount();
}
}
二、synchronized 用法
synchronized 的用法只有兩種:修飾方法、修飾代碼塊
1、在方法聲明時使用,修飾方法
注意:這個synchronized 修飾符 不會參與方法簽名的比較;
語法:
public synchronized void synMethod() {
//方法體
}
有以下兩種情況:
- 1.1、修飾的方法是普通的成員方法,那么是對象鎖,便是以當前對象為鎖,即調用這個方法的對象
- 1.2、修飾的方法是靜態方法,則是類鎖。
public synchronized static int countData(int data){
return data*data;
}
2、修飾一個代碼塊
語法:
public int synMethod(int a1){
synchronized( object ) {
//代碼塊,一次只能有一個線程進入
}
}
有以下3種情況:
2.1、object 是 this,是對象鎖,this指代當前對象
public int getAmount(){
//對象鎖,以當前對象為鎖
synchronized (this) {
int amount = price*num;
return amount;
}
}
2.2、object 是一個普通對象實例
- 如果是靜態對象,那么就是 類鎖;
- 如果是非靜態對象:成員對象變量、局部變量(甚至可以是 方法參數),那么就是對象鎖;
public void setObj(){
FruitCount fruitCount = null;
//局部變量
synchronized(fruitCount){
fruitCount = new FruitCount();
}
}
2.3、object 是一個類的class 對象,那么就是類鎖
public void setData(int price,int num){
//類鎖,以FruitCount.class為鎖
synchronized(FruitCount.class){
this.price = price;
this.num = num;
}
}
小 結:
1、出現類鎖的情況:
- 以 類.class 為鎖
- 以 靜態變量為鎖
- 修飾靜態方法
2、出現對象鎖的情況:
- 以實例成員對象為鎖(特殊:this 指當前對象)
- 以局部變量(甚至是方法傳進來的參數)為鎖、
- 修飾成員方法
3、當synchronized修飾方法時,synchronized是不參與 方法簽名的比較;