Java多執行緒4—執行緒同步問題+火車票售票系統
在上一篇文章中寫到了許多執行緒共享同一資料,這種情況在現實的生活中也是經常發生的,比如火車站的火車票售票系統。火車票售票系統是一個常年執行的系統,為了滿足乘客的需求,我們不能只設一個視窗,必須設很多的售票視窗,每個售票視窗就像一個執行緒,它們各自執行,共同訪問相同的資料——火車票的數量,下面我們用多執行緒模仿一下火車票售票系統:
public
class TicketSystem
{
public static void main(String[] args)
{
SellThread st=new SellThread();
new
Thread(st).start();new Thread(st).start();new Thread(st).start();}
}
class
SellThread implements Runnable{
int tickets=100;
public void run()
{
while(true)
{
if(tickets>0)
{
System.out.println("obj:"+Thread.currentThread
().getName()+" sell tickets:"+tickets);tickets--;
}
}
}
}
程式輸出了執行緒0、1、2三個執行緒從第100張票賣到第1張票的過程,這個程式正確的輸出了。但是實際情況中火車票售票系統是常年執行的,有可能第一顧客已經買到了最後一張票,但是執行到tickets--的時候,執行緒切換了,這時候另外一個顧客也買最後一張票,這時候系統顯示還有票,這時候這個顧客也訂票了,並且也在執行到tickets--的時候,執行緒切換。再回去執行原來的執行緒,前個顧客買到了最後一張票,後一個顧客但是卻買到了第0張票。顯然這個是不對的。下面我們修改上面程式模擬一下:
class
SellThread implements Runnable{
int tickets=100;
public void run()
{
while(true)
{
if(tickets>0)
{
Try//讓執行緒睡眠10毫秒,只是修改了這裡
{
Thread.sleep(10);
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
System.out.println("obj:"+Thread.currentThread().getName()+" sell tickets:"+tickets);
tickets--;
}
}
}
}
結果的一部分如下:
obj:Thread-0 sell tickets:2
obj:Thread-2 sell tickets:2
obj:Thread-1 sell tickets:0
obj:Thread-0 sell tickets:-1
我們可以看出上面程式兩次售出了第二張票,並且賣出了第0張和-1張票,顯然這個結果是不對的。遇到了不對的情況,我們就應該得想辦法解決它,在Java中運用同步的方法解決這個問題。
在介紹之前我們先說一下臨界區,在Java中程式碼段訪問了同一個物件或資料,那麼這個程式碼段就叫做臨界區。上面程式中run()方法中迴圈裡面的程式碼就叫做臨界區。我們需要對這個臨界區進行保護,這樣就用到了執行緒的同步。
在Java中執行緒的同步有兩種方法,一種是同步塊,另一種是同步方法。不管是哪種方法都用到了synchronized關鍵字。下面我們就修改上面的程式用例項顯示一下:
class SellThread implementsRunnable
{
int tickets=100;
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)//加上一個同步關鍵字
{
if(tickets>0)
{
try{
Thread.sleep(10);
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
System.out.println("obj:"+Thread.currentThread().getName()+" sell tickets:"+tickets);
tickets--;
}
}
}
}
}
上面使用synchronized關鍵字的時候需要一個物件,這裡的物件可以是任意物件,我們在這裡選擇的是Object物件,你也可以選擇String類的物件。每一個物件都有一個監視器或者叫鎖。當我將obj作為引數傳給synchronized關鍵字的時候,就相當於給這段程式碼加了一個鎖,當我執行到這段程式碼的時候,先判斷是否加鎖,如果沒有加鎖,先將其加鎖然後執行程式碼。如果加鎖了只能等待。等到給程式碼加鎖的執行緒執行完成之後,會將鎖開啟。
下面我們介紹同步方法,我們還是以程式的形式給出:
class SellThread implements Runnable
{
int tickets=100;
Object obj = new Object();
public synchronized void sell()//用關鍵字sychronized //關鍵字標誌同步方法
{
if(tickets>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
e.printStackTrace();
}
System.out.println("sell():"+Thread.currentThread().getName()+
" sell tickets:"+tickets);
tickets--;
}
}
public void run()
{
while(true)
{
sell();
}
}
}
上面的程式用sychronized關鍵字直接標誌的是sell()方法,我們在run()方法裡呼叫sell()的時候,sell()方法中的程式碼就相當於一個整體都一起執行。但是同步方法也是使用加鎖的方法進行同步的,它不像同步塊那樣傳遞一個物件,那麼他是對哪個物件加的鎖呢?同步方法是對程式中的this物件加鎖以實現同步的。
我們有時候會寫一些靜態的方法,這些方法也必須同步。靜態方法只屬於類本身,沒有this物件,那麼它對誰加鎖呢?前面我們介紹過每一個類都有一個Class類的物件(詳細介紹請看:http://blog.csdn.net/mengxiangyue/article/details/6831820),靜態方法正是對這個Class類的物件進行加鎖以實現同步方法的。
對於同步方法就先介紹到這裡,如果有錯請大家指出,希望對大家有幫助。