胡泳霞(廣州科技貿(mào)易職業(yè)學(xué)院,廣東廣州,511442)
?
基于內(nèi)存模型的Java并發(fā)編程
胡泳霞
(廣州科技貿(mào)易職業(yè)學(xué)院,廣東廣州,511442)
摘要:多核處理器為并發(fā)編程打開了一扇扇新的大門,Java內(nèi)置的多線程機制可以方便地實現(xiàn)多個并發(fā)程序的開發(fā)以及多任務(wù)同時執(zhí)行,但是Java線程之間的通信對程序員完全透明,內(nèi)存可見性問題很容易困擾Java開發(fā)人員,本文將簡單分析基于內(nèi)存模型的Java并發(fā)編程。
關(guān)鍵詞:Java并發(fā);內(nèi)存模型;多線程;同步機制
并發(fā)在我們的現(xiàn)實世界中隨處可見,以至于我們常常忽略了它的存在。比如我們可以在聽歌的時候看書,看電影的時候吃薯片等等。
多核處理器的發(fā)展促進了并發(fā)編程。如果軟件或者服務(wù)想要使用不斷增強的處理器處理能力,需要使用并發(fā)編程。在計算機編程中,并發(fā)編程是一個非常重要的解耦合策略,它幫助我們把做什么和什么時候做分開。這樣做可以明顯改進應(yīng)用程序的吞吐量和結(jié)構(gòu)(程序有多個部分在協(xié)同工作)。
目前來說,并發(fā)編程的實現(xiàn)方式一種是多進程的并發(fā),另一種是多線程的并發(fā)。從操作系統(tǒng)的角度來看,進程是資源分配的基本單位,線程是任務(wù)調(diào)度的基本單位,線程是輕量級的進程但它不能脫離進程存在,也就是說線程使用的資源都是從宿主進程獲得的。Java中我們說的并發(fā)編程一般就是指多線程編程。
1.1多線程并發(fā)
1.1.1線程
Java中實現(xiàn)多線程有兩種方法:繼承Thread類、實現(xiàn)Runnable接口。
1.1.2多線程
一個多線程程序包含兩個或更多的能并行運行的部分,并且每一部分能最優(yōu)利用可用資源,尤其是當(dāng)你的計算機有多個 CPU時,同時解決不同的任務(wù)。但是如果把我們在單線程中運行的程序不加改造地拿到多線程中去,很有可能是不會有正確結(jié)果的。如以下代碼(見下圖1):
UnsafeAccount類主要代碼如下:private long balance;public long deposit(long someMoney){},public long withdraw(long someMoney){},public long getBalance() {}.上面的例子中,小明在之前的單線程支出/存入銀行賬號時,都沒有出現(xiàn)問題。但在并發(fā)操作銀行賬號時,賬上的余額就不正常了。在對象UnsafeAccount中“balance += someMoney”和“balance -=someMoney”中,"balance"為共享變量,且對于Java說,“+=”和“-=”并非原子操作,實際是三個獨立操作。而你永遠不知道每個線程在何時運行,運行哪個操作,故原來的對象線程不安全。
1.2線程同步機制
圖1
線程之間會共享一些對象,我們稱之為狀態(tài),當(dāng)多線程同時讀寫某個共享狀態(tài)時可能會因不恰當(dāng)?shù)膱?zhí)行時序而造成程序邏輯的混亂,如何保證共享狀態(tài)的互斥(即保證任意時刻某個共享狀態(tài)只能由單個線程訪問,即原子性)和同步(當(dāng)前線程的值都是上一線程執(zhí)行完后的最新的值,即可見性)。
1.2.1synchronized
Java中的同步塊用synchronized標記方法或者代碼塊是同步的。所有同步在一個對象上的同步塊同時只能被一個線程進入并執(zhí)行操作,所有其他等待進入該同步塊的線程將被阻塞,直到執(zhí)行該同步塊中的線程退出。
將 上 述UnsafeAccount類 的 方 法 修 改:public synchronized long deposit(long someMoney) {},public synchronized long withdraw(long someMoney){},public synchronized long getBalance() {}.
Synchronized關(guān)鍵字可以保證共享狀態(tài)的原子性和可見性,但是鎖住了整個代碼,效率在某些場景可能不佳。在Java中,還可以通過volatile、鎖等方式實現(xiàn)同步。
1.2.2volatile
Java語中的volatile變量可以被看作是一種“輕量級synchronized”;與synchronized塊相比,volatile變量所需的編碼較少,并且運行時開銷也較少,但是它所能實現(xiàn)的功能也僅是synchronized 的一部分(可見性)。
將上述UnsafeAccount 類的成員屬性作修改:private volatile long balance;
如上述代碼。利用synchronized的原子性(共享狀態(tài)的更新)和volatile的可見性(共享狀態(tài)的最新值)混合使用,保證共享狀態(tài)的線程安全。
Java內(nèi)存模型規(guī)范了Java虛擬機與計算機內(nèi)存是如何協(xié)同工作的。如果你想設(shè)計表現(xiàn)良好的并發(fā)程序,理解Java內(nèi)存模型是非常重要的。Java內(nèi)存模型規(guī)定了如何和何時可以看到由其他線程修改過后的共享變量的值,以及在必要時如何同步的訪問共享變量。
可見性的問題是Java多線程并發(fā)異常的常見的根源。在一個單線程程序中,如果首先改變一個變量的值,再讀取該變量的值的時候,所讀取到的值就是上次寫操作寫入的值。也就是說前面操作的結(jié)果對后面的操作是肯定可見的。但是在多線程程序中,如果不使用一定的同步機制,就不能保證一個線程所寫入的值對另外一個線程是可見的。
基于上述,回到剛才的例子,重新解釋一下小明不安全的銀行賬號問題:如在“RP爆發(fā),自家網(wǎng)店同一時刻100個訂單確認收貨,每單1元(包郵)”的某個時刻下的線程A、B,balance=3300:
在時刻T4,線程B把線程A在時刻T3寫入主內(nèi)存的balance給覆蓋了或者說時刻T3,線程B的本地副本已經(jīng)是臟數(shù)據(jù)了。
2.1happens-before規(guī)則
在JMM中(Java5以后),如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,那么這兩個操作之間必須要存在happensbefore關(guān)系,但要happens-before僅僅要求前一個操作(執(zhí)行的結(jié)果)對后一個操作可見,且前一個操作按順序排在第二個操作之前。兩個操作既可以是在一個線程之內(nèi),也可以是在不同線程之間。
happens-before規(guī)則如下:
①程序順序規(guī)則:一個線程中的每個操作,happensbefore 于該線程中的任意后續(xù)操作。
②監(jiān)視器鎖規(guī)則:對一個監(jiān)視器鎖的解鎖,happensbefore 于隨后對這個監(jiān)視器鎖的加鎖。
③volatile變 量 規(guī) 則:對 一 個volatile域 的 寫,happens-before 于任意后續(xù)對這個volatile域的讀。
④傳遞性:如果A happens-before B,且B happensbefore C,那么A happens-before C。
接下來以JMM內(nèi)存模型的角度去解釋一下小明的例子,synchronized如何保證共享狀態(tài)的線程安全的。
比如:其中2個線程A、B,線程A執(zhí)deposit(),此刻后線程B執(zhí)行withdraw()。
public synchronized long deposit(long someMoney) { //1
balance += someMoney;//2
return balance;//3
}//4
public synchronized long withdraw(long someMoney) {//5
balance -= someMoney;//6
return balance;//7
}//8
①程序順序規(guī)則:1 happens before 2, 2 happens before 3, 3 happens before 4; 5 happens before 6,6 happens before 7,7 happens before 8。
②監(jiān)視器鎖規(guī)則: 4 happens before 5
③傳遞性:1 happens before 5,那么A happens before B。
并發(fā)編程相對于一般的串行編程來說更具復(fù)雜性,風(fēng)險性。不恰當(dāng)?shù)牟l(fā)編程,不僅讓程序效率得不到提高,還可能得到非預(yù)期的結(jié)果,并且存在此類并發(fā)問題,排查解決起來相當(dāng)耗時。所以Java程序員應(yīng)該深入了解并發(fā)編程的原理機制及其內(nèi)存模型,更好編寫高效安全的Java并發(fā)程序。
參考文獻
[1]戈茨等.JAVA并發(fā)編程實踐(M).北京:電子工業(yè)出版社,2007.
[2]周志遠,張大方,繆力. 基于Java內(nèi)存模型的并發(fā)程序模型檢測[J].計算機工程與科學(xué).2010(03).
作者簡介
胡泳霞,女(1989.12)廣東省興寧市,畢業(yè)于廣東技術(shù)師范學(xué)院,工作單位廣州科技貿(mào)易職業(yè)學(xué)院。
The Java Concurrent Programming Based On Memory Model
Hu Yongxia
(Guangzhou Vocational College of Technology & Business,Guangdong Guangzhou,511442)
Abstract:The multi-core processor opens a new door for the concurrent programming.You can implement multiple concurrent programs and multiple tasks at the same time simultaneously by Java built-in multithreading mechanism.But the communication between the Java thread is completely transparent to the programmer,which is easy to puzzle the Java developer.In this paper,I will simply analyze the Java concurrent programming based on memory model.
Keywords:Java concurrency;memory model;multi-thread;synchronization mechanism