synchronized關鍵字

一. 概念

(synchronized) java 中的關鍵字,是利用鎖的機制來實現同步的。

特性

  1. 互斥性:即在同一時間只也許一個線程持有某個對象鎖,同一時間只有一個線程對需要同步的代碼塊進行訪問。
  2. 必須確保在鎖被釋放之前,對共享變量所做的修改,對於隨後獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作從而引起不一致。

二. 對象鎖和類鎖

對象鎖

在 Java 中,每個對象都會有一個 monitor 對象,這個對象其實就是 java 對象的鎖(內置鎖/對象鎖),每個對象都有獨立的對象鎖,互不干擾。

類鎖

在 Java 中,針對每個類也有一個鎖,稱爲“類鎖”,每一個類只有一個 class 對象鎖。

三 synchronized 的用法

根據修飾的對象分類

synchronized 可以修飾方法和代碼塊

  • 修飾代碼塊
    • synchronized(this.object){}
    • synchronized(類.class)
  • 修飾方法
    • 修飾靜態方法
    • 修飾非靜態方法

根據獲取的鎖分類

  • 獲取對象鎖
    • synchronized(this|object){}
    • 修飾非靜態方法
  • 獲取類鎖
    • synchronized(類.class)
    • 修飾非靜態方法/靜態方法

//修飾非靜態方法
public synchronized void test1() {}
//修飾靜態方法
public synchronized static void test() {}

四. synchronized 例子

1. 對象鎖

public class SynchorizedThread extends Thread {
    @Override
	public void run() {
		syn5();
	}
	public void syn5() {
		 System.out.println(Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
		 
	        synchronized (new SynchorizedThread()) {
	            try {
	                System.out.println(Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	                Thread.sleep(2000);
	                System.out.println(Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	        }
	}
}

後面所有的例子都是採用這四個線程,只是對方法進行修改

public static void main(String args[]) {
		SynchorizedThread syncThread = new SynchorizedThread();
        Thread F_thread1 = new Thread(new SynchorizedThread(), "F_thread1");
        Thread F_thread2 = new Thread(new SynchorizedThread(), "F_thread2");
        Thread F_thread3 = new Thread(syncThread, "F_thread3");
        Thread F_thread4 = new Thread(syncThread, "F_thread4");
        F_thread1.start();
        F_thread2.start();
        F_thread3.start();
        F_thread4.start();
	}

運行結果如下:

F_thread2_Sync5: 08:49:36
F_thread1_Sync5: 08:49:36
F_thread4_Sync5: 08:49:36
F_thread2_Sync5_Start: 08:49:36
F_thread3_Sync5: 08:49:36
F_thread4_Sync5_Start: 08:49:36
F_thread1_Sync5_Start: 08:49:36
F_thread3_Sync5_Start: 08:49:36
F_thread1_Sync5_End: 08:49:38
F_thread3_Sync5_End: 08:49:38
F_thread4_Sync5_End: 08:49:38
F_thread2_Sync5_End: 08:49:38

四個線程同時開始,同時結束,因爲作爲鎖的對象與線程是屬於不同的實例

2. 採用類鎖,無論哪個類都會被攔截

public class SynchorizedThread extends Thread {
    @Override
	public void run() {
		syn5();
	}
	public void syn5() {
		 System.out.println(Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
		 
	        synchronized (test.class) {
	            try {
	                System.out.println(Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	                Thread.sleep(2000);
	                System.out.println(Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	        }
	}
}

運行結果如下:

F_thread2_Sync5: 09:26:25
F_thread3_Sync5: 09:26:25
F_thread4_Sync5: 09:26:25
F_thread1_Sync5: 09:26:25
F_thread2_Sync5_Start: 09:26:25
F_thread2_Sync5_End: 09:26:27
F_thread3_Sync5_Start: 09:26:27
F_thread3_Sync5_End: 09:26:29
F_thread4_Sync5_Start: 09:26:29
F_thread4_Sync5_End: 09:26:31
F_thread1_Sync5_Start: 09:26:31
F_thread1_Sync5_End: 09:26:33

可以發現,採用類鎖一次只能通過一個,即使是不同的類,也只能通過一個。

3. 採用 this 對象鎖

public class SynchorizedThread extends Thread {
    @Override
	public void run() {
		syn5();
	}
	public void syn5() {
		 System.out.println(Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
		 
	        synchronized (this) {
	            try {
	                System.out.println(Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	                Thread.sleep(2000);
	                System.out.println(Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	        }
	}
}

結果如下:

F_thread2_Sync5: 09:40:29
F_thread1_Sync5: 09:40:29
F_thread4_Sync5: 09:40:29
F_thread1_Sync5_Start: 09:40:29
F_thread3_Sync5: 09:40:29
F_thread2_Sync5_Start: 09:40:29
F_thread4_Sync5_Start: 09:40:29
F_thread1_Sync5_End: 09:40:31
F_thread2_Sync5_End: 09:40:31
F_thread4_Sync5_End: 09:40:31
F_thread3_Sync5_Start: 09:40:31
F_thread3_Sync5_End: 09:40:33

可以發現1,2同時結束,3是等4結束後纔開始,它們是同一對象

4. 修飾方法

作用範圍是整個方法

  1. 非靜態方法
public class SynchorizedThread extends Thread {
    @Override
	public void run() {
		syn5();
	}
	public synchronized void syn5() {
		 System.out.println(Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	            try {
	                System.out.println(Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	                Thread.sleep(2000);
	                System.out.println(Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	        
	}
}

結果爲:

F_thread2_Sync5: 09:44:45
F_thread3_Sync5: 09:44:45
F_thread1_Sync5: 09:44:45
F_thread2_Sync5_Start: 09:44:45
F_thread1_Sync5_Start: 09:44:45
F_thread3_Sync5_Start: 09:44:45
F_thread3_Sync5_End: 09:44:47
F_thread1_Sync5_End: 09:44:47
F_thread2_Sync5_End: 09:44:47
F_thread4_Sync5: 09:44:47
F_thread4_Sync5_Start: 09:44:47
F_thread4_Sync5_End: 09:44:49

可以發現和this對象鎖很像,同一個實例的線程訪問會被攔截,非同一實例可以同時訪問
2. 修飾靜態方法

public class SynchorizedThread extends Thread {
    @Override
	public void run() {
		syn5();
	}
	public static synchronized void syn5() {
		 System.out.println(Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	            try {
	                System.out.println(Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	                Thread.sleep(2000);
                    System.out.println(Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	}
}

結果爲:

F_thread1_Sync5: 09:47:50
F_thread1_Sync5_Start: 09:47:50
F_thread1_Sync5_End: 09:47:52
F_thread4_Sync5: 09:47:52
F_thread4_Sync5_Start: 09:47:52
F_thread4_Sync5_End: 09:47:54
F_thread3_Sync5: 09:47:54
F_thread3_Sync5_Start: 09:47:54
F_thread3_Sync5_End: 09:47:56
F_thread2_Sync5: 09:47:56
F_thread2_Sync5_Start: 09:47:56
F_thread2_Sync5_End: 09:47:58

可以發現是和類鎖一樣

總結

  1. 對於靜態方法,由於此時對象還未生成,採用類鎖。
  2. 只要是類鎖,就會攔截所有的線程,只能讓一個線程訪問。
  3. 對於對象鎖(this/Object),如果是同一個實例,就會按照順序進行訪問,但是如果是不同的實例,就能同時訪問
  4. 如果對象鎖跟訪問的對象沒有關係,那麼就都會同時訪問。
  5. synchronized 關鍵字不能被繼承。對於父類中的 synchronized 修飾的方法,子類覆蓋的時候,默認情況是不同步的,必須使用 synchronized 關鍵字修飾才行。
  6. 定義接口時候不能使用 synchronized 關鍵字
  7. 構造方法不能使用 synchronized 關鍵字,但可以使用 synchronized 代碼塊同步。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章