1. 程式人生 > 其它 >Java併發程式設計-八鎖問題帶你徹底理解物件鎖和類鎖

Java併發程式設計-八鎖問題帶你徹底理解物件鎖和類鎖

技術標籤:多執行緒併發程式設計Javajava

八鎖問題帶你理解物件鎖和類鎖

8鎖問題演示

1.標準訪問

/*手機類可以發郵件和發簡訊*/
class
Phone{ public synchronized void sendEmail() throws Exception{ System.out.println("***sendEmail"); } public synchronized void sendSMS() throws Exception{ System.out.println("***sendSMS"); } } public class Lock8Demo { public static void main(String[] args) throws InterruptedException {
//建立一個資源類 Phone phone=new Phone(); new Thread(()->{ try { phone.sendEmail(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } },"A").start(); Thread.sleep(100); new Thread(()->{ try { phone.sendSMS(); } catch (Exception
e) { // TODO Auto-generated catch block e.printStackTrace(); } },"B").start(); } }

在這裡插入圖片描述
標準訪問,先列印郵件還是簡訊.
不一定誰先被列印。取決於CPU的執行情況.啟用他們的是main執行緒,後面誰先被排程,不知道。
為了保證效果,我們在A和B的程式碼之間加thread.sleep(100).此時,可以確保A先被列印。
解釋:
只要一個資源類裡面,不管它有多少個同步方法,只要一個執行緒先訪問了資源類裡面的任何一個同步方法,那麼它鎖的不是這一個方法,鎖的是該方法所在的整個資源類。也就是說,鎖的是物件。它鎖的不是當前的方法。
也就是說,這些synchoronized的方法都屬於同一個資源類裡面,鎖的是整個資源類。

2.在郵件方法中暫停4秒,請問先列印郵件還是簡訊


/*手機類可以發郵件和發簡訊*/
class Phone{
	public synchronized void sendEmail() throws Exception{
		TimeUnit.SECONDS.sleep(4);		//表示暫停4秒,它是一個列舉型別
		System.out.println("***sendEmail");
	}
	public synchronized void sendSMS() throws Exception{
		System.out.println("***sendSMS");
	}
}

public class Lock8Demo {
	public static void main(String[] args) throws InterruptedException {
		//建立一個資源類
		Phone phone=new Phone();
		new Thread(()->{
			try {
				phone.sendEmail();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"A").start();
		Thread.sleep(100);
		new Thread(()->{
			try {
				phone.sendSMS();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"B").start();
	}
}

先列印郵件。同步方法獲取的phone物件鎖,sleep不會釋放鎖。到了時間會立即執行。所以先列印郵件方法
解釋:
它與問題1類似。
只要一個資源類裡面,不管它有多少個同步方法,只要一個執行緒先訪問了資源類裡面的任何一個同步方法,那麼它鎖的不是這一個方法,鎖的是該方法所在的整個資源類。也就是說,鎖的是物件。它鎖的不是當前的方法。
舉個例子:我和班長要用同一個手機打電話,我肯定要等班長打完電話才能接著打。班長用的過程中停網了一段時間,那我也只能等班長打完。

3.新增普通sayHello方法,請問先列印郵件還是hello

先列印hello

/*手機類可以發郵件和發簡訊*/
class Phone{
	public synchronized void sendEmail() throws Exception{
		TimeUnit.SECONDS.sleep(4);		//表示暫停4秒,它是一個列舉型別
		System.out.println("***sendEmail");
	}
	public synchronized void sendSMS() throws Exception{
		System.out.println("***sendSMS");
	}
	public void sayHello(){
		System.out.println("***sayHello");
	}
}

public class Lock8Demo {
	public static void main(String[] args) throws InterruptedException {
		//建立一個資源類
		Phone phone=new Phone();
		new Thread(()->{
			try {
				phone.sendEmail();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"A").start();
		Thread.sleep(100);
		new Thread(()->{
			try {
				//phone.sendSMS();
				phone.sayHello();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"B").start();
	}
}

在這裡插入圖片描述
解釋:加個普通方法後發現和同步鎖無關。因此它無需等待同步鎖釋放。
這裡可以舉一個例子。班長用它的手機要打電話。而我要向班長藉手機充電線,這兩個不互斥,因此可以在班長打電話完之前就借走充電線。

4.兩部手機,請問先列印郵件還是簡訊

/*手機類可以發郵件和發簡訊*/
class Phone{
	public synchronized void sendEmail() throws Exception{
		TimeUnit.SECONDS.sleep(4);		//表示暫停4秒,它是一個列舉型別
		System.out.println("***sendEmail");
	}
	public synchronized void sendSMS() throws Exception{
		System.out.println("***sendSMS");
	}
	public void sayHello(){
		System.out.println("***sayHello");
	}
}

public class Lock8Demo {
	public static void main(String[] args) throws InterruptedException {
		//建立一個資源類
		Phone phone=new Phone();
		Phone phone2=new Phone();
		new Thread(()->{
			try {
				phone.sendEmail();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"A").start();
		Thread.sleep(100);
		new Thread(()->{
			try {
				//phone.sendSMS();
				//phone.sayHello();
				phone2.sendSMS();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"B").start();
	}
}

在這裡插入圖片描述

解釋:這裡可以舉一個例子,班長用它的手機法郵件。我用我自己的手機打電話。那麼誰先誰後就沒有關係。

5.兩個靜態同步方法,同一部手機,請問先列印郵件還是簡訊

/*手機類可以發郵件和發簡訊*/
class Phone{
	public static synchronized void sendEmail() throws Exception{
		TimeUnit.SECONDS.sleep(4);		//表示暫停4秒,它是一個列舉型別
		System.out.println("***sendEmail");
	}
	public static synchronized void sendSMS() throws Exception{
		System.out.println("***sendSMS");
	}
	public void sayHello(){
		System.out.println("***sayHello");
	}
}

public class Lock8Demo {
	public static void main(String[] args) throws InterruptedException {
		//建立一個資源類
		Phone phone=new Phone();
		Phone phone2=new Phone();
		new Thread(()->{
			try {
				phone.sendEmail();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"A").start();
		Thread.sleep(100);
		new Thread(()->{
			try {
				phone.sendSMS();
				//phone.sayHello();
				//phone2.sendSMS();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"B").start();
	}
}

在這裡插入圖片描述
解釋:可以與問題6一起分析

6.兩個靜態同步方法,兩部手機,請問先列印郵件還是簡訊

/*手機類可以發郵件和發簡訊*/
class Phone{
	public static synchronized void sendEmail() throws Exception{
		TimeUnit.SECONDS.sleep(4);		//表示暫停4秒,它是一個列舉型別
		System.out.println("***sendEmail");
	}
	public static synchronized void sendSMS() throws Exception{
		System.out.println("***sendSMS");
	}
	public void sayHello(){
		System.out.println("***sayHello");
	}
}

public class Lock8Demo {
	public static void main(String[] args) throws InterruptedException {
		//建立一個資源類
		Phone phone=new Phone();
		Phone phone2=new Phone();
		new Thread(()->{
			try {
				phone.sendEmail();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"A").start();
		Thread.sleep(100);
		new Thread(()->{
			try {
				//phone.sendSMS();
				//phone.sayHello();
				phone2.sendSMS();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"B").start();
	}
}

在這裡插入圖片描述
解析:
static屬於一個類。也就說,他不屬於當前物件this的一個獨立的個體。而是屬於全域性的class。這就要考慮物件鎖和全域性鎖的區別。全域性鎖即類鎖。此時不管是一個phone還是多個phone都來自於同一個類Phone類。現在不管你鎖到了哪個物件,我都要等他釋放完了這個鎖才能使用。

7.1個靜態同步方法,1個普通同步方法,同一部手機,請問先列印郵件還是簡訊

/*手機類可以發郵件和發簡訊*/
class Phone{
	public static synchronized void sendEmail() throws Exception{
		TimeUnit.SECONDS.sleep(4);		//表示暫停4秒,它是一個列舉型別
		System.out.println("***sendEmail");
	}
	public synchronized void sendSMS() throws Exception{
		System.out.println("***sendSMS");
	}
	public void sayHello(){
		System.out.println("***sayHello");
	}
}

public class Lock8Demo {
	public static void main(String[] args) throws InterruptedException {
		//建立一個資源類
		Phone phone=new Phone();
		Phone phone2=new Phone();
		new Thread(()->{
			try {
				phone.sendEmail();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"A").start();
		Thread.sleep(100);
		new Thread(()->{
			try {
				phone.sendSMS();
				//phone.sayHello();
				//phone2.sendSMS();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"B").start();
	}
}

在這裡插入圖片描述
一個靜態,一個普通。鎖的是同一部手機。靜態方法鎖的是Class.相當於我們鎖了一個校門,一個是普通同步方法,鎖的是當前物件。比如教師普通的們。鎖的物件不一樣。就不衝突

8.1個靜態同步方法,1個普通同步方法,兩部手機,請問先列印郵件還是簡訊

/*手機類可以發郵件和發簡訊*/
class Phone{
	public static synchronized void sendEmail() throws Exception{
		TimeUnit.SECONDS.sleep(4);		//表示暫停4秒,它是一個列舉型別
		System.out.println("***sendEmail");
	}
	public synchronized void sendSMS() throws Exception{
		System.out.println("***sendSMS");
	}
	public void sayHello(){
		System.out.println("***sayHello");
	}
}

public class Lock8Demo {
	public static void main(String[] args) throws InterruptedException {
		//建立一個資源類
		Phone phone=new Phone();
		Phone phone2=new Phone();
		new Thread(()->{
			try {
				phone.sendEmail();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"A").start();
		Thread.sleep(100);
		new Thread(()->{
			try {
				//phone.sendSMS();
				//phone.sayHello();
				phone2.sendSMS();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"B").start();
	}
}

在這裡插入圖片描述

解釋:這個跟上面是一樣的。

8鎖理論解釋

1.一個物件裡面如果有多個syncrhonized方法,某一時刻內,只要有一個執行緒去呼叫其中的一個synchronized方法了,其他的執行緒都只能等待,換句話說,某一時刻內,只能有唯一一個執行緒去訪問這些synchronized方法。
鎖的是當前物件this,被鎖定後,其他的執行緒都不能進入到當前物件的其他synchronized方法

加個普通方法後發現和同步鎖無關。
換乘兩個物件後,不是同一把鎖了,情況立刻發生變化。
都換成靜態同步方法後,情況立刻變化
所有的非靜態同步方法用的都是同一把鎖------例項物件本身

2.synchronized實現同步的基礎:java中的每個物件都可以作為鎖。
具體表現為以下3種形式:

  1. 對於普通同步方法,鎖是當前例項物件
  2. 對於同步方法塊,鎖是synchronized括號裡面配置的物件。
    比如在方法裡寫
    synchronized(this){
    }
  3. 對於靜態同步方法,鎖是當前類的Class物件。

當一個程序試圖訪問同步程式碼塊時,它首先必須得到鎖,退出或丟擲異常必須釋放鎖。
也就是說一個例項物件的非靜態同步方法獲取鎖後,該例項物件的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖後才能獲取鎖。可是別的例項物件的非靜態同步方法因為跟該例項物件的非靜態同步方法用的是不同的鎖,所以無需等待該例項物件已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。

所有的靜態同步方法用的也是同一把鎖-----類物件本身。
這兩把鎖是兩個不同的物件,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。(問題78)。即一個鎖class,一個鎖this,兩者不衝突。
但是一旦一個靜態同步方法獲取鎖後,其他的靜態同步方法都必須等待該方法釋放鎖後才能獲取鎖。而不管是同一個例項物件的靜態方法之間,還是不同的例項物件的靜態同步方法之間,只要它們同一個類的例項物件。(問題56)