曾經有一個比較有趣的面試問題,那就是,關於使用synchronized
關鍵字,是用在方法上面尾號,還是用在一個代碼塊上面爲好?
答案就是使用鎖定代碼塊爲更好。因爲這樣不會鎖定對象。當synchronized
關鍵字在實例方法的上面時,線程對於該方法的訪問會直接鎖定整個對象,參考如下代碼:
class Sync {
public synchronized void syncMethod() {
System.out.println("Sync Method Executed");
}
public void syncThis() {
synchronized (this) {
System.out.println("Sync This Executed");
}
}
}
上面的類中,syncMethod()
和syncThis()
方法,是沒有區別的。爲了驗證這個說法,參考如下代碼:
package net.ethanpark.common.task;
/**
* Author: EthanPark <br/>
* Date: 2016/11/27<br/>
*/
public class SyncTest {
public static void main(String[] args) {
Sync sync = new Sync();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
sync.syncMethod();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
sync.syncThis();
}
});
t1.start();
t2.start();
}
}
class Sync {
public synchronized void syncMethod() {
System.out.println("Sync Method Start To Executed");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Sync Method Executed");
}
public void syncThis() {
synchronized (this) {
System.out.println("Sync This Executed");
}
}
}
再說上面的代碼中,我啓動了兩個線程,分別執行syncMethod()
和syncThis()
方法,其中syncMethod()
方法會等待5秒,然後才執行結束。以下是輸出的結果:
Sync Method Start To Executed
Sync Method Executed
Sync This Executed
t1和t2是先後啓動的,如果syncMethod()
方法沒有鎖定Sync對象的話,syncThis()
方法肯定會更優先的執行,但是syncThis()
方法從輸出來看,在獲取this
對象的鎖的時候,阻塞了,直到syncMethod()
全部執行完畢纔開始執行,所以纔有如上的輸出。
在考慮前面那個問題就清晰了,synchronized
直接加在方法上面,會鎖定整個對象,也就是說,如果對象中有一些字段是不需要同時來同步的,synchronized
關鍵字也會阻止其他線程來獲取該對象的鎖,從而令其他線程阻塞而無法達到高吞吐量。
當然,synchronized
關鍵字對對象加鎖,也只有其他對象也在請求對象鎖的時候纔會阻塞的,如果其他對象不需要請求對象鎖,而是直接訪問非同步變量,還是允許的。針對之前的代碼我增加了一個新的線程和一個新的方法notSyncMethod()
:
package net.ethanpark.common.task;
/**
* Author: EthanPark <br/>
* Date: 2016/11/27<br/>
*/
public class SyncTest {
public static void main(String[] args) {
Sync sync = new Sync();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
sync.syncMethod();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
sync.syncThis();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
sync.notSyncMethod();
}
});
t1.start();
t2.start();
t3.start();
}
}
class Sync {
public synchronized void syncMethod() {
System.out.println("Sync Method Start To Executed");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Sync Method Executed");
}
public void syncThis() {
synchronized (this) {
System.out.println("Sync This Executed");
}
}
public void notSyncMethod(){
System.out.println("Not Sync Method Executed");
}
}
程序的輸出如下:
Sync Method Start To Executed
Not Sync Method Executed
Sync Method Executed
Sync This Executed
從第一行輸出來看,t1其實已經獲得了sync
對象的對象鎖,但是notSyncMethod()
方法仍然是可以執行的。
需要注意的是,synchronized
方法也是可以加在靜態方法上面的。然而加在靜態方法上面和加在實例方法上面,兩者是不會使得線程互斥的。原因很簡單,針對實例方法同步,我們請求的是對象鎖,而靜態方法的訪問,是不針對對象的,所以兩者是不會衝突的。針對靜態方法的同步,實際上跟針對類型的同步是一致的。參考如下代碼:
package net.ethanpark.common.task;
/**
* Author: EthanPark <br/>
* Date: 2016/11/27<br/>
*/
public class SyncTest {
public static void main(String[] args) {
Sync sync = new Sync();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
sync.syncMethod();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Sync.staticSyncMethod();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
Sync.staticTypeMethod();
}
});
t1.start();
t2.start();
t3.start();
}
}
class Sync {
public synchronized void syncMethod() {
System.out.println("Sync Method Start To Executed");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Sync Method Executed");
}
public synchronized static void staticSyncMethod(){
System.out.println("Static Sync Method Start To Executed");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Static Sync Method Executed");
}
public static void staticTypeMethod(){
synchronized (Sync.class){
System.out.println("Static Sync Type Executed");
}
}
}
上面代碼的輸出如下:
Sync Method Start To Executed
Static Sync Method Start To Executed
Static Sync Method Executed
Static Sync Type Executed
Sync Method Executed
可以看出,在staticSyncMethod()
和staticTypeMethod()
兩者是等同的,當staticSyncMethod()
執行的時候,staticTypeMethod()
是阻塞的。而兩個靜態的方法,跟實例的方法是完全互不影響的。