一、Synchronized的作用
主要是保證多線程環境下的線程安全。
二、Synchronized種類
1. 對象鎖
- 包含方法鎖(默認鎖對象爲this當前實力對象),方法鎖形式:synchronized修飾普通方法,鎖默認對象爲this
public class MySynchronized {
public synchronized void m1(){
}
}
上面這種方法可以被:下面這種方式替代:
public void m3(){
synchronized(this){ //synchronized 需要包住m3 所有的內部代碼片段
}
}
- 同步代碼塊鎖(自己制定鎖對象)代碼塊形式:手動指定鎖對象
public class MySynchronized {
public void m1(){
synchronized (this){
}
}
或者如下
public class MySynchronized {
Object obj= new Object();
public void m1(){
synchronized(obj){
}
}
2. 類鎖
指sychronized修飾靜態的方法或指鎖爲Class對象,
概念: java類可能有有很多個對象,但是隻有一個class對象
本質: 所以所謂的類鎖,不過是Class對象的鎖而已
用法和效果:類鎖只能在同一時刻被一個對象擁有
形式1:synchronized加載static方法上
public class MySynchronized {
public synchronized static void m1() {
}
}
形式2:synchronized(*.class)代碼塊
public class MySynchronized {
public void m2() {
synchronized (MySynchronized.class) {
}
}
}
三、常見多線程情況下Synchronized的舉例
-
兩個線程同時訪問一個對象的相同的synchronized方法
串行 -
兩個線程同時訪問兩個對象的相同的synchronized方法
鎖對象不同,互不干擾,並行 -
兩個線程同時訪問兩個對象的相同的static的synchronized方法
串行 -
兩個線程同時訪問同一對象的synchronized方法與非synchronized方法
並行 -
兩個線程訪問同一對象的不同的synchronized方法
同一對象鎖,串行 -
兩個線程同時訪問同一對象的static的synchronized方法與非static的synchronized方法
鎖不同,並行 -
方法拋異常後,會釋放鎖嗎
如果一個線程在進入同步方法後拋出了異常,則另一個線程會立刻進入該同步方法 -
目前進入到被synchronized修飾的方法,這個方法裏邊調用了非synchronized方法,是線程安全的嗎?
線程安全
四、synchronized的性質
1. 可重入性
指的是同一線程的外層函數獲取鎖之後,內層函數可以直接再次獲取該鎖
Java典型的可重入鎖:synchronized、ReentrantLock
好處:避免死鎖,提升封裝性
粒度範圍證明:
情況一:同一方法是可重入的—遞歸調用本方法
/**
* 可重入測試----使用遞歸方式
* 情況1:同一個類中,同一方法可重入
*/
public class SynchronizedTest {
int i = 0;
//主線程可以重入以this爲鎖對象的method方法
public static void main(String[] args) {
SynchronizedTest s1 = new SynchronizedTest();
s1.method();
}
// 同步方法
private synchronized void method() {
if (i <=3) {
i++;
System.out.println(i);
method();
}
}
}
輸出:
1
2
3
4
情況二:可重入不要求是同一個方法
/**
* 可重入測試----
* 情況2:同一個類中,不同方法可重入
*/
public class SynchronizedTest2 {
//主線程可以重入以this爲鎖對象的method方法
public static void main(String[] args) {
SynchronizedTest2 s1 = new SynchronizedTest2();
s1.method1();
}
// 同步方法
private synchronized void method1() {
System.out.println("method1");
method2();
}
private synchronized void method2() {
System.out.println("method2");
}
}
輸出:
method1
method2
情況三:可重入不要求是同一個類中的
public class Demo1 {
public synchronized void method(){
System.out.println("我是Demo1");
}
}
class Demo1Zi extends Demo1{
public synchronized void method(){
System.out.println("我是Demo1兒子");
super.method();
}
public static void main(String[] args) {
Demo1Zi zi = new Demo1Zi();
zi.method();
}
}
輸出:
我是Demo1兒子
我是Demo1
2. 不可中斷性
一旦這個鎖被別的線程獲取了,如果我現在想獲得,我只能選擇等待或者阻塞,直到別的線程釋放這個鎖,如果別的線程永遠不釋放鎖,那麼我只能永遠的等待下去。
相比之下,Lock類可以擁有中斷的能力,第一點:如果我覺得我等待的時間太長了,有權中斷現在已經獲取到鎖的線程執行;第二點:如果我覺得我等待的時間太長了不想再等了,也可以退出。
五、synchronized的缺點
1、效率低(jdk1.6對其進行了優化)
1)、鎖的釋放情況少(線程執行完成或者異常情況釋放)
2)、試圖獲得鎖時不能設定超時(只能等待)
3)、不能中斷一個正在試圖獲得鎖的線程(不能中斷)
jdk1.6對synchronized進行了優化,有一個鎖升級的過程,使得效率有一個提升,但是鎖升級不可逆。
2、不夠靈活
加鎖和釋放的時機比較單一,每個鎖僅有單一的條件(某個對象),可能是不夠的
比如:讀寫鎖更靈活
六、如何選擇Lock和synchronized關鍵字
本着優先避免出錯的原則,得出以下結論:
- 如果可以的話,儘量優先使用java.util.concurrent各種類(不需要考慮同步工作,不容易出錯)
- 優先使用synchronized,這樣可以減少編寫代碼的量,從而可以減少出錯率
- 若用到Lock或Condition獨有的特性,才使用Lock或Condition
七. 總結
JVM會自動通過使用monitor來加鎖和解鎖,保證了同一時刻只有一個線程可以執行指定的代碼,從而保證線程安全,同時具有可重入和不可中斷的特性。
八. 參考鏈接
阿里面試:跟我死磕Synchronized底層實現,我滿分回答拿了Offer