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