[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這一種同步。