1. 程式人生 > >[22]Synchronized程式碼塊與Synchronized函式

[22]Synchronized程式碼塊與Synchronized函式

一、使用原因

多執行緒使用原因:

一塊大蛋糕太大了,一天內還需要吃完,不然就壞了,一個人吃不完,所以需要兩個人吃,但是隻有一個勺子,如果A吃的時候不小心把勺子弄丟了。需要花時間找,那麼B就不能吃。所以就需要使用多執行緒。給兩個勺子,就算A吃的時候丟了,B也可以不受到影響。繼續執行下去。

同步鎖的使用原因:

吃蛋糕的時候,因為兩個人不停的吃。A吃的快,B吃的慢。就導致了,A吃撐了了,B反而沒吃飽。不是我們想看到的事情。所以需要一個搶盤子(同步鎖),A搶到盤子先吃四分之一,B等著。等A吃完後,B繼續搶。如果搶到了,B吃。如果沒搶到,那麼A再吃四分之一後,就讓給B。

二、程式碼體現

同步程式碼塊:

synchronized(Object obj){

      //code

}

 同步方法:

public synchronized void method(){//code}

三、程式碼案例

package com.synchronized_test;

/**
 * 人類
 */
public class Person implements Runnable {
	/* 蛋糕物件 */
	private Cakes c = new Cakes();

	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized (c) {
			while(c.num>0) {
				eat();
				try {
                    //問題!!!後面有講解
					c.wait(1000);//讓A執行緒停一下,給B執行緒得到執行的機會
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
    /*吃蛋糕的動作*/
	public void eat() {
		System.out.println(Thread.currentThread().getName() + " eat第" + c.num-- + "份");
	}	
}

package com.synchronized_test;

/**
 * 蛋糕類
 * */
public class Cakes{
	/*數量,分4份*/
	int num=4;
}

package com.synchronized_test;
/**
 * 測試
 * */
public class Demo {
	public static void main(String[] args) {
		Person p=new Person();
		
		Thread A=new Thread(p);
		Thread B=new Thread(p);
		
		A.start();
		B.start();
	}
}

程式碼解釋:

1.共有3個類,測試類,蛋糕類,人類

2.蛋糕類:分成4份。所以屬性就是num,為了讓程式碼稍微優化點,就沒封裝了。

3.人類:執行緒類,行為是eat(),還有一個實現Runnable介面的run()。eat()中寫的是列印輸出哪個執行緒吃的,並且使數量減一。run()中寫是隻要num>0就不停迴圈。

4.測試類:建立Person類例項,建立兩個執行緒,並將p作為引數傳入兩個執行緒中,開啟後,兩個執行緒進入就緒狀態。CPU會隨機 給予執行權。

問題為什麼要寫wait(long millis)而不是sleep(long millis),兩個程式碼作用很像。但是會使兩個執行緒處於不同的狀態。

sleep(long millis):執行緒暫時處於TIME_WAITING狀態,但是不釋放物件鎖。也就是說,我就算不繼續執行,但我手裡還是握著這個物件的鎖。我就算不執行,你也別想執行。那麼如果這裡寫sleep(),也只會使A執行緒執行4次而已。B執行緒一口都吃不到。

wait():執行緒釋放物件鎖,且處於WAITING狀態,除非被手動喚醒,否則將不會擁有CPU的執行資格,更別說CPU執行權了。

wait(long millis):執行緒釋放物件鎖,處於TIME_WAITING狀態,與無參的wait()的區別就是等到millis的時間到了之後,會被自動喚醒。重新爭奪物件鎖。

四、使用區別

既然同步程式碼塊和同步方法都能同步。那使用哪個?

程式碼案例:

package com.synchronized_methods;
/**
 * Synchronized1
 * */
public class SynchronizedMethod {
	private AnotherClass a=new AnotherClass();
	/*同步程式碼塊1*/
	public void showA() {
		System.out.println("showA");
		synchronized(a) {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	/*同步函式2*/
	/*同步函式的物件鎖是固定的this,跟ShowA不是一個物件鎖*/
	public synchronized void showB() {
		System.out.println("showB");
	}
	/*同步程式碼塊2*/
	/*與showB的物件鎖是同一個物件鎖*/
	public void showC() {
		synchronized(this) {
			System.out.println("showC");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

package com.synchronized_methods;

/**
 * Synchronized2
 * */

public class AnotherClass {
	/*同步程式碼塊1*/
	public void showA() {
		/*與SynchronizedMethod中的showA方法是同一把鎖*/
		synchronized (this) {
			System.out.println("showA2");
		}
	}
}

package com.synchronized_methods;

/**
 * 測試類
 * */

public class Demo {
	public static void main(String[] args) {
		SynchronizedMethod s=new SynchronizedMethod();
		AnotherClass a=new AnotherClass();
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				s.showA();
				a.showA();
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				s.showC();
				s.showB();
			}
		}).start();
	}
}

執行結果:

執行情況:先執行了showA()和showC()後,執行了隔了1秒執行了showB(),再隔了2秒執行了showA()

showA
showC
showB
showA2

程式碼解釋:

問題1:為什麼執行了showA()後沒有暫停又直接執行了showC()?

解答:很明顯,Synchronized程式碼塊就是為了物件鎖不讓其他執行緒在執行中間插入。所以之所以能直接執行showC(),完全是因為兩個方法用的不是同一個鎖。showA()用的是AnotherClass類的物件,而showC()用的是當前物件。所以能夠無視showA()方法中的Thread.sleep(3000)

問題2:那為什麼可以之後停了1秒之後又執行了showB()呢?

解答:因為showB()中,沒有寫同步程式碼塊,而是用同步修飾了方法,所以之所以等1秒,也是因為同步方法的物件鎖,跟showC()中的物件鎖是同一個物件鎖。所以說,同步方法的物件鎖其實就是this

 

五、全文總結:

1、同步程式碼塊:允許同一個執行緒中,同步的時候,可以有多種同步情況。因為程式碼塊中的引數為Object obj

2、同步方法:只允許this這一種同步。