李娟
摘 要:Java多線程同步機制的應用有利于提高系統(tǒng)資源的利用率,改善系統(tǒng)的安全性。但是在多線程中最重要的問題是線程的同步和共享資源的訪問保護。本文通過具有意義的售票系統(tǒng)的并發(fā)同步實例,對同步進行了探索。
關鍵詞:Java 多線程 同步
中圖分類號:G420 文獻標識碼:A 文章編號:1673-9795(2014)03(a)-0183-02
至今,隨著計算機技術的飛速發(fā)展和互聯(lián)網的大面積普及,多處理器計算機已經司空見慣,在這種前景下,Java虛擬機(JVM)提供了一個多線程機制。在Java語言的編程設計中使用多線程運行機制來支持多任務和并行處理,可以讓在同一地址空間中執(zhí)行多控制流,顯著的提高程序效率。但是線程的同步問題和共享資源的訪問保護是非常復雜的問題。
1 線程的同步機制
多線程的應用程序中,兩個或兩個以上的線程可以共享同一片存儲空間,這帶來方便的同時,也導致線程共享資源發(fā)生沖突,此時我們可以使用Java語言提供的同步機制(又叫互斥鎖機制)來解決此沖突問題。該同步機制是使用synchronized關鍵字控制一段程序代碼,這代碼段稱為互斥區(qū)或臨界區(qū)。定義臨界區(qū)的目的是在任一時間只有一個線程使用共享資源,保證多線程的并發(fā)執(zhí)行。Java語言的每個對象(即類實例)都對應一把鎖(Lock),臨界區(qū)使用鎖來互斥多線程進入臨界區(qū)。每次只有一個線程獲得鎖進入臨界區(qū),其它沒有獲得鎖的線程必須在就緒隊列中等待,直到該鎖被釋放。synchronized關鍵字的使用方式有synchronized方法和塊兩種。
(1)synchronized方法:將訪問共享資源的方法都標記為synchronized,然后該標記的方法來控制對類成員變量的訪問。類實例和鎖是一一對應的,當獲得需要調用synchronized方法的類實例鎖時,synchronized方法才可以執(zhí)行,而且它開始執(zhí)行直到完畢為止獨占鎖。這時其它調用synchronized方法的線程進入阻塞,一直到獲得釋放鎖為止。定義同步方法語法格式如下:
public synchronized void 方法名(參數(shù)列表){
…//省略代碼
}
(2)synchronized塊:java語言中除了使用synchronized方法來設置同步,還可以使用synchronized塊來設置同步。如果使用前者來修飾一個比較大的方法時,也會鎖住了不需要鎖住的字段,導致程序運行效率降低。后者是把程序的某段代碼使用synchronized塊來修飾,跟前者比它可以減少程序的同步區(qū)域。所以我們可以使用synchronized塊來修飾語句塊,能夠彌補synchronized方法修飾的缺陷。定義同步塊的語法格式如下:
synchronized(表達式)//表達式的結果是當前對象{
…//省略代碼
}
從以上兩種方法能夠看出,關鍵字synchronized用來與對象的鎖聯(lián)系,當某個對象使用synchronized修飾時就意味著同步機制已啟動,任一時刻只有讓一個線程訪問臨界區(qū)資源,阻止其他線程訪問該對象,即使出現(xiàn)阻塞和死鎖現(xiàn)象,該對象的被鎖定狀態(tài)也不會解除。
2 同步機制在售票系統(tǒng)的實現(xiàn)
在現(xiàn)實生活當中也經常遇到多個線程共享同一個數(shù)據(jù)資源,典型的例子是火車票售票系統(tǒng),來講解線程共享資源。假設在售票廳內設10個售票窗口,每個售票窗口相當于一個線程,這些線程的共同訪問資源為售票廳的100張票。若不設置同步機制代碼如下:
public class Ticket {
public static void main(String[] args) {
Sell_Ticket st = new Sell_Ticket();//創(chuàng)建10個線程,每個線程代表一個售票口
for (int i = 0; i < 10; i++) new Thread(st, "第" + i + "個窗口").start();
}
}
class Sell_Ticket implements Runnable {
int trainTicket = 100;//預售的票數(shù)
boolean flag = false;//循環(huán)控制標志
public void run(){
while (!flag) {// 當還有剩余票時繼續(xù)售票
sellTicket(); }
}
public void sellTicket(){
if (trainTicket > 0) {
System.out.println(Thread.currentThread().getName()+ "售票成功,剩余票數(shù):"
+ trainTicket);
trainTicket--;
} else flag = true;
}
}
運行的結果是多個窗口同時售票,會出現(xiàn)剩余票數(shù)變?yōu)樨摂?shù)的情況,即10個線程從100張票賣到1張票的時候還沒有停止賣,系統(tǒng)出現(xiàn)繼續(xù)賣出負數(shù)票的現(xiàn)象。
下面通過Java的多線程同步機制的synchronized方法和塊兩種方式來分別解決以上出現(xiàn)的問題。為了便于觀察到運行錯誤,特意添加Thread.sleep(10)方法,讓每個線程在售票階段睡眠10 ms。
(1)使用synchronized方法:在售票方法sellTicket()的前面添加synchronized關鍵字,就相當于使用一把鎖鎖住該方法。修改后的程序代碼如下:
public synchronized void sellTicket(){
if (trainTicket > 0) {
try {
Thread.sleep(10);//睡眠10毫秒
} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+ "售票成功,剩余票數(shù):"+ trainTicket);
trainTicket--;
} else flag = true;
}
以上是使用synchronized關鍵字修飾sellTicket()方法,對該方法實現(xiàn)了多線程的互斥訪問。程序運行run()方法以后,當判斷出還有剩余票時調用被加鎖的sellTicket()方法,這時的sellTicket()方法,在同一個時間段內只能被一個線程訪問。
若synchronized關鍵字修飾靜態(tài)方法sellTicket(),鎖住的就是類本身。因為靜態(tài)方法是所有類實例對象所共享的,因此線程對象在訪問此靜態(tài)方法時是互斥訪問的,從而可以實現(xiàn)線程的同步。實現(xiàn)方法如下所示:
public static synchronized void sellTicket() {}
(2)使用synchronized塊:在程序中的if語句外面加synchronized關鍵字,就相當于使用一把鎖鎖住了這段代碼。它不同于同步方法,傳遞一個對象進行同步。修改后的程序如下:
Object object =new Object();
public void run() {
while (!flag) { // 當還有剩余票時繼續(xù)售票
synchronized(object){
if (trainTicket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+ "售票成功,剩余票數(shù):"
+ trainTicket);
trainTicket--;
} else flag = true;
}
}
}
以上是使用synchronized關鍵字修飾了售票代碼段,對該代碼段實現(xiàn)了多線程的互斥訪問。同步塊不像同步方法修飾整個方法,而修飾一段代碼即可。synchronized(object)傳遞的是一個對象object,如果多線程想使用該對象的方法和變量,首先判斷有沒有加鎖,若已加鎖,等待鎖的釋放;若沒有鎖,先將給它加鎖,然后去執(zhí)行代碼。在同一個時間段內只能有一個線程能夠獲得這把鎖。
同步塊的關鍵是多個線程對象競爭同一個共享資源即可,上面的代碼中是通過外部創(chuàng)建共享資源,然后傳遞到線程中來實現(xiàn)。我們也可以利用類成員變量被所有類的實例所共享這一特性,因此可以將object對象用靜態(tài)成員對象來實現(xiàn),如下所示:
static Object object =new Object();
使用synchronized方法和塊兩種方式修改后的運行結果相同,剩余票數(shù)每次減1,從100減到0,到0時flag=true,while循環(huán)結束,即不能售票。
3 結語
Java程序中通過synchronized關鍵字來實現(xiàn)互斥訪問。本文引用的售票系統(tǒng),通過synchronized方法和塊兩種方式實現(xiàn)了線程的同步互斥,避免了車票售完以后還能繼續(xù)售票導致數(shù)據(jù)混亂的問題。總之,合理使用多線程同步機制才能讓數(shù)據(jù)資源得到安全保障。
參考文獻
[1] 明日科技.Java從入門到精通[M].3版.北京:清華大學出版社,2013.
[2] 沈祥玖,李作緯.操作系統(tǒng)原理與應用[M].3版.北京:高等教育出版社,2013.
[3] 路勇.Java多線程同步問題分析[J].軟件,2012(4):31-33.