Java併發程式設計(詳解wait(), notify(),sleep())
上一篇部落格,重點講解了java中鎖的機制,省的在多執行緒之間出現混亂的局面,其實主要能夠理解鑰匙即可。如果要保證方法之間能夠獨立完全的執行,因此就必須所有的方法都共用一把鑰匙。然後小編最後也總結了一下,在此也再說一下。
1.呼叫同一個物件中非靜態同步方法的執行緒將彼此阻塞。如果是不同物件,則每個執行緒有自己的物件的鎖,執行緒間彼此互不干預。
2.呼叫同一個類中的靜態同步方法的執行緒將彼此阻塞,他們都所鎖在同一個class物件上
這篇部落格來講一下,java中執行緒之間的同步技術,用到的也就是Object物件的幾個方法,正如標題所示wait、notify,sleep方法,為了學習執行緒之間的同步技術,小編查閱了很多部落格,在此也分享一下自己的學習經驗。
這裡詳細的說一下各個方法的說明
1.notify()
具體是怎麼個意思呢?就是用來喚醒在此物件上等待的單個執行緒。說的有點太專業。打個比方,現在有十棟大房子,裡面有很多被上了鎖的房間,奇怪的是鎖都是一樣的,更不可思議的是,現在只有一把鑰匙。而此時,張三用完鑰匙後,就會發出歸還鑰匙的提醒,就相當於發出notify()通知,但是要注意的是,此時鑰匙還在張三手中,只不過,當張三發出notify()通知後,JVM從那些整個沉睡的執行緒,喚醒一個。對應本例子,就是從其餘的九棟大房子中喚醒一家,至於提醒誰來拿這把鑰匙,就看JVM如何分配資源了。等到張三把鑰匙歸還後,那個被提醒的哪家,就可以使用該把鑰匙來開房間門了。與此相對應的,還有一個notifyAll()方法。這是什麼意思呢,還是本例,張三嗓門大,這時吼了一嗓子,即notifyAll(),所有沉睡的執行緒全都被吵醒了,當張三歸還鑰匙後,他們就可以競爭了,注意,剛才是JVM自動分配,而此時是執行緒之間競爭,比如優先順序等等條件,是有區別的。
需要注意的是notify()方法執行後,並不是立即釋放鎖,而是等到加鎖的程式碼塊執行完後,才開始釋放的,相當於本例中,張三隻是發出了歸還的通知,但是鑰匙還沒有歸還,需要等到程式碼塊,執行完後,才可以歸還。
2.wait()
這個方法又是怎麼個意思呢?當執行到這個方法時,就把鑰匙歸還,開始睡覺了。Thread.sleep()與Object.wait()二者都可以暫停當前執行緒,釋放CPU控制權,主要的區別在於Object.wait()在釋放CPU同時,釋放了物件鎖的控制。
下面來看一個例子,加入我們要實現三個執行緒之間的同步操作,如何來實現呢?加入有三個執行緒分別用來輸出A/B/C,如何能夠三個執行緒之間順序執行呢?這時候就需要採取執行緒之間同步的操作了,詳見下面的程式碼。
<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;
public class MyThreadPrinter2 implements Runnable {
private String name;
private Object prev;
private Object self;
private MyThreadPrinter2(String name, Object prev, Object self) {
this.name = name; // A B C
this.prev = prev; // c a b
this.self = self; // a b c
}
@Override
public void run() {
int count = 10;
while (count > 0) {
// 加鎖,鎖的鑰匙是prev
synchronized (prev) {
// 一把鎖,鎖的鑰匙是self變數
synchronized (self) {
System.out.print(name);
count--;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 喚醒另一個執行緒,但是也需要把本執行緒執行完後,才可以釋放鎖
self.notify(); //a b c
}
try {
// 釋放物件鎖,本執行緒進入休眠狀態,等待被喚醒
prev.wait(); //睡覺覺了,等待被叫醒吧 // c a b
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
new Thread(pa).start();
// 這樣才可以保證按照順序執行
Thread.sleep(10);
new Thread(pb).start();
Thread.sleep(10);
new Thread(pc).start();
Thread.sleep(10);
}
}</span>
下面來分析一下,首先通過執行緒之間的同步操作,上面的例子就會按照執行緒的順序來分別執行,最終輸出的結果就是ABCABCABC..............。
在此還是打一個比方,程式剛開始的時候,建立了三個執行緒的物件,也就是代表有三座大房子,下面開始執行了,第一個大房子裡面有一個叫做張三的人員,打開了一個房子的鑰匙c,然後拿著鑰匙a,又一次打開了這個房子裡面的一個箱子,最後完成後,把箱子的鑰匙a給歸還了,執行完後,張三開始在這個c鑰匙的房子裡面漫長的沉睡,也就是prev.wati()方法;當第二個人李四來到房子後,同樣執行一邊操作,此時需要注意的是,李四扔出的是箱子鑰匙b,並在a鑰匙的房間隨著了;好吧懶貨,王五來到了第三個大房子,同樣執行了一遍操作,注意的是,王五扔出的是箱子鑰匙c,在鑰匙b房子睡著了。
重點來了,王五扔出箱子鑰匙c後,此時就把在房子鑰匙c中的張三給喚醒了,等到王五把鑰匙歸還後,此時張三又開始周而復始的運作了;於是大家可以按照邏輯接著向下分析一下。
在整個例子中需要注意一下幾點
1.為了保證幾個執行緒先能夠順序執行,於是加入了Thread.sleep(10)
2.每個物件執行wait()或者notify()方法時,只能在同一把鎖的房子裡面,例如
synchronized (self) {
System.out.print(name);
count--;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 喚醒另一個執行緒,但是也需要把本執行緒執行完後,才可以釋放鎖
self.notify(); //a b c
}
房子的鎖是self,所以執行self.notify()代表把我房子的鑰匙給歸還了。
有了上面的分析過程,下面我們出一道題,傳統執行緒同步通訊技術,子執行緒迴圈10次,接著主執行緒迴圈100次,又回到子執行緒迴圈10次,接著再回到主執行緒又迴圈100次,如此迴圈50次。這個例子又如何來實現呢?小編只在這裡貼出原始碼,網上有很多解釋的,可以按照小編的邏輯來分析一下。
<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;
public class TraditionalThreadCommunication {
/**
* @param args
*/
public static void main(String[] args) {
final Business business = new Business();
new Thread(
new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
business.sub(i);
}
}
}
).start();
for(int i=1;i<=50;i++){
business.main(i);
}
}
}
class Business {
private boolean bShouldSub = true;
public synchronized void sub(int i){
while(!bShouldSub){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("sub thread sequence of " + j + ",loop of " + i);
}
bShouldSub = false;
this.notify();
}
public synchronized void main(int i){
while(bShouldSub){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j=1;j<=100;j++){
System.out.println("main thread sequence of " + j + ",loop of " + i);
}
bShouldSub = true;
this.notify();
}
}
</span>