對象鎖和類鎖的區別


生活中不存在絕對的自由,絕對的自由通常對應的無序和混沌,只有在道德、法律、倫理的約束下的相對自由,才能使人感受到自由。
而在多線程編程中,鎖是至關重要的,鎖就是道德,就是法律約束,沒有鎖的多線程環境將會是混亂的,所有線程都在爭奪資源,最後的結果就是導致系統崩潰,而有了鎖之後,多線程環境才能穩定高效的工作。

synchronized 關鍵字

synchronized 是我們所說的重量級鎖,所說的重量級是相對於那些自旋鎖(AQS)而言的,比如可重入鎖ReentrantLock。很多人談 synchronized 色變,說它性能差、太重,貌似言之鑿鑿。放在多年前確實如此,但是 Java 1.7、1.8 已經對 synchronized 做了很大優化,其性能和那些輕量級鎖幾乎沒有差距。
所以,我們再程序中其實可以放心的使用它,即使沒有用過,也肯定在一些源碼裏見過,比如 Netty 中就有很多地方用到了它。
下面開始進入今天的主題,類鎖和實例鎖。看名字就已經很明顯了,類鎖就是所在類上的鎖,實例就是鎖在類實例上的鎖。

對象鎖

類聲明後,我們可以 new 出來很多的實例對象。這時候,每個實例在 JVM 中都有自己的引用地址和堆內存空間,這時候,我們就認爲這些實例都是獨立的個體,很顯然,在實例上加的鎖和其他的實例就沒有關係,互不影響了。

通常我們使用實例鎖的方式有下面三種:

1、鎖住 this 對象

this 指的是當前對象實例本身,所以,所有使用 synchronized(this)方式的方法都共享同一把鎖。
在這裏插入圖片描述

2、 鎖住實體裏的非靜態變量

非靜態變量是實例自身變量,不會與其他實例共享,所以鎖住實體內聲明的非靜態變量可以實現對象鎖。鎖住同一個變量的方法塊共享同一把鎖。
在這裏插入圖片描述

3、直接鎖非靜態方法

最簡單、最直觀的一種方式,直接加在方法返回類型前。
在這裏插入圖片描述

使用對象鎖的情況,只有使用同一實例的線程纔會受鎖的影響,多個實例調用同一方法也不會受影響。

對象鎖代碼測試

下面來做個測試,開啓 5 個線程,每個線程都 new 一個新的實例來分別調用上面三種方式的方法,方法完成的動作就是輸出線程名稱,然後休眠 10 秒鐘。

public class ObjectLock {

	private Object lock = new Object();
	
	/**     
	* 鎖住非靜態變量     
	* @throws InterruptedException     
	*/    
	public void lockObjectField() throws InterruptedException {        
		synchronized (lock) {            
			System.out.println(Thread.currentThread().getName());            
			Thread.sleep(10*1000);        
		}    
	}    

	/**     
	* 鎖住 this 對象 this 就是當前對象實例     
	* @throws InterruptedException     
	*/    
	public void lockThis() throws InterruptedException {        
		synchronized (this) {            
			System.out.println(Thread.currentThread().getName());            
			Thread.sleep(10*1000);        
		}    
	}   
	 /**     
	 * 直接鎖住非靜態方法     
	 * @throws InterruptedException     
	 */    
	public synchronized void methodLock() throws InterruptedException {   
		System.out.println(Thread.currentThread().getName());        
	 	Thread.sleep(10*1000);    
	}   
  
	public static void main(String[] args) {        
		for (int i = 0; i < 5; i++) {            
	  		Thread worker = new Thread(new ObjectLockWorker());            
	  		worker.setName("kite-" + i);            
	  		worker.start();        
	  	}    
	}    

	public static class ObjectLockWorker implements Runnable {        
		@Override        
	  	public void run() {           
			try {               
	    		ObjectLock objectLock = new ObjectLock();                
	    		// 方式 1                
	    		objectLock.lockObjectField();                
	    		// 方式 2                
	    		//objectLock.lockThis();                
	    		// 方式 3                
	    		//objectLock.methodLock();            
	    	} catch (Exception e) {                
	    		e.printStackTrace();           
	    	}        
	    }    
	}

}

我們預測的結果就是每個線程都會立刻輸出線程名稱,然後各自休眠 10 秒。

分別調用方式1、2、3,效果都是一樣的,我們看到輸出結果和我們預測的是一樣的,5 個線程都立即輸出線程名,然後等待 10 秒,整個程序退出。

類鎖

類鎖是加載類上的,而類信息是存在 JVM 方法區的,並且整個 JVM 只有一份,方法區又是所有線程共享的,所以類鎖是所有線程共享的。

使用類鎖的方式有如下方式:

1、鎖住 xxx.class

對當前類的 .class 屬性加鎖,可以實現類鎖。
在這裏插入圖片描述

2、鎖住類中的靜態變量

因爲靜態變量和類信息一樣也是存在方法區的並且整個 JVM 只有一份,所以加在靜態變量上可以達到類鎖的目的。
在這裏插入圖片描述

3、直接在靜態方法上加 synchronized

因爲靜態方法同樣也是存在方法區的並且整個 JVM 只有一份,所以加在靜態方法上可以達到類鎖的目的。
在這裏插入圖片描述

類鎖是所有線程共享的鎖,所以同一時刻,只能有一個線程使用加了鎖的方法或方法體,不管是不是同一個實例。

類鎖代碼測試

下面同樣來做個測試,開啓 5 個線程,除了調用靜態方法的方式,其他兩種方式中每個線程都 new 一個新的實例來分別調用,方法內完成的動作就是輸出線程名稱,然後休眠 10 秒鐘。

public class ClassLock { 

	private static Object lock = new Object();

	/**
	 * 鎖住靜態變量
	 *
	 * @throws InterruptedException
	 */
	public void lockStaticObjectField() throws InterruptedException {
		synchronized (lock) {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(10 * 1000);
		}
	}
	
	/**
	 * 鎖住靜態方法
	 *
	 * @throws InterruptedException
	 */
	public static synchronized void methodLock() throws InterruptedException {
		System.out.println(Thread.currentThread().getName());
	  	Thread.sleep(10 * 1000);
	}
	
	/**
	 * 鎖住 xxx.class
	 *
	 * @throws InterruptedException
	 */
	public void lockClass() throws InterruptedException {
		synchronized (ClassLock.class) {
	    	System.out.println(Thread.currentThread().getName());
	    	Thread.sleep(10 * 1000);
	  	}
	}
	
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
	   		Thread worker = new Thread(new ClassLockWorker());
	    		worker.setName("kite-" + i);
	    		worker.start();
	 	 }
	}
	
	public static class ClassLockWorker implements Runnable {
		@Override
	  	public void run() {
	    	try {
	      		ClassLock classLock = new ClassLock();
	      		// 方式 1
				// classLock.lockStaticObjectField();
				// 方式 2
				// ClassLock.methodLock();
				// 方式 3
				// classLock.lockClass();
	    	} catch (Exception e) {
	      		e.printStackTrace();
	    	}
	  	}
	}


}

我們預測的結果就是剛開始只有1個線程搶到鎖,然後輸出線程名,之後等待 10 秒中,之後是下一個搶到鎖的線程,輸出線程名,然後等待 10 秒。直到最後一個搶到鎖的線程,整個過程歷時大約 50 秒。

分別調用方式1、2、3,觀察執行結果,和我們預測的是一致的。

總結

  • 使用對象鎖的情況,只有使用同一實例的線程纔會受鎖的影響,多個實例調用同一方法也不會受影響。

  • 類鎖是所有線程共享的鎖,所以同一時刻,只能有一個線程使用加了鎖的方法或方法體,不管是不是同一個實例。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章