Java 并发基础——线程安全性
線程安全:多個(gè)線程訪問(wèn)某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)調(diào),這個(gè)類都能表現(xiàn)出正確的行為,那么久稱這個(gè)類是線程安全的。
在線程安全類中封裝了必要的同步機(jī)制,因此客戶端無(wú)需采取進(jìn)一步的同步措施。
原子性
要么不執(zhí)行,要么執(zhí)行到底。原子性就是當(dāng)某一個(gè)線程修改i的值的時(shí)候,從取出i到將新的i的值寫給i之間不能有其他線程對(duì)i進(jìn)行任何操作。也就是說(shuō)保證某個(gè)線程對(duì)i的操作是原子性的,這樣就可以避免數(shù)據(jù)臟讀。 通過(guò)鎖機(jī)制或者CAS(Compare And Set 需要硬件CPU的支持)操作可以保證操作的原子性。
當(dāng)多個(gè)線程訪問(wèn)某個(gè)狀態(tài)變量,并且其中有一個(gè)線程執(zhí)行寫入操作時(shí),必須采用同步機(jī)制來(lái)協(xié)調(diào)這些線程對(duì)變量的訪問(wèn)。無(wú)狀態(tài)對(duì)象一定是線程安全的。
? 如果我們?cè)跓o(wú)狀態(tài)的對(duì)象中增加一個(gè)狀態(tài)時(shí),會(huì)出現(xiàn)什么情況呢?假設(shè)我們按照以下方式在servlet中增加一個(gè)"命中計(jì)數(shù)器"來(lái)管理請(qǐng)求數(shù)量:在servlet中增加一個(gè)long類型的域,每處理一個(gè)請(qǐng)求就在這個(gè)值上加1。
public class UnsafeCountingFactorizer implements Servlet {
private long count = 0;
public long getCount() {
return count ;
}
@Override
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
// do something
count++;
}
}
不幸的是,以上代碼不是線程安全的,因?yàn)閏ount++并非是原子操作,實(shí)際上,它包含了三個(gè)獨(dú)立的操作:讀取count的值,將值加1,然后將計(jì)算結(jié)果寫入count。如果線程A讀到count為10,馬上線程B讀到count也為10,線程A加1寫入后為11,線程B由于已經(jīng)讀過(guò)count值為10,執(zhí)行加1寫入后依然為11,這樣就丟失了一次計(jì)數(shù)。
??????? 在 count++例子中線程不安全是因?yàn)?count++并非原子操作,我們可以使用原子類,確保確保操作是原子,這樣這個(gè)類就是線程安全的了。
public class CountingFactorizer implements Servlet {
private final AtomicLong count = new AtomicLong(0);
public long getCount() {
return count .get() ;
}
@Override
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
// do something
count.incrementAndGet();
}
}
?
?????? AtomicLong是java.util.concurrent.atomic包中的原子變量類,它能夠?qū)崿F(xiàn)原子的自增操作,這樣就是線程安全的了。?? 同樣,上述情況還會(huì)出現(xiàn)在 單例模式的懶加載過(guò)程中,當(dāng)多個(gè)線程同時(shí)訪問(wèn) getInstance()函數(shù)時(shí)。這篇文章中有講解:實(shí)現(xiàn)優(yōu)雅的單例模式
加鎖機(jī)制
????? 線程在執(zhí)行被synchronized修飾的代碼塊時(shí),首先檢查是否有其他線程持有該鎖,如果有則阻塞等待,如果沒有則持有該鎖,并在執(zhí)行完之后釋放該鎖。
????? 除了使用原子變量的方式外,我們也可以通過(guò)加鎖的方式實(shí)現(xiàn)線程安全性。還是UnsafeCountingFactorizer,我們只要在它的service方法上增加synchronized關(guān)鍵字,那么它就是線程安全的了。當(dāng)然在整個(gè)方法中加鎖在這里是效率很低的,因?yàn)槲覀冎恍枰WCcount++操作的原子性,所以這里只對(duì)count++進(jìn)行了加鎖,代碼如下:
?
public class UnsafeCountingFactorizer implements Servlet {private long count = 0;public long getCount() {return count ;}@Overridepublic void service(ServletRequest arg0, ServletResponse arg1)throws ServletException, IOException {// do somethingsynchronized(this){count++;}} }?
Synchronized代碼塊使得一段程序的執(zhí)行具有 原子性,即每個(gè)時(shí)刻只能有一個(gè)線程持有這個(gè)代碼塊,多個(gè)線程執(zhí)行在執(zhí)行時(shí)會(huì)互不干擾。
java 內(nèi)存模型及 可見性
? ? ?Java的內(nèi)存模型沒有上面這么簡(jiǎn)單,在Java Memory Model中,Memory分為兩類,main memory和working memory,main memory為所有線程共享,working memory中存放的是線程所需要的變量的拷貝(線程要對(duì)main memory中的內(nèi)容進(jìn)行操作的話,首先需要拷貝到自己的working memory,一般為了速度,working memory一般是在cpu的cache中的)。被volatile修飾的變量在被操作的時(shí)候不會(huì)產(chǎn)生working memory的拷貝,而是直接操作main memory,當(dāng)然volatile雖然解決了變量的可見性問(wèn)題,但沒有解決變量操作的原子性的問(wèn)題,這個(gè)還需要synchronized或者CAS相關(guān)操作配合進(jìn)行。
每個(gè)線程內(nèi)部都保有共享變量的副本,當(dāng)一個(gè)線程更新了這個(gè)共享變量,另一個(gè)線程可能看的到,可能看不到,這就是可見性問(wèn)題。
下面這段代碼中 main 線程中 改變了 ready的值,當(dāng)開啟多個(gè)子線程時(shí),子線程的值并不是馬上就刷新為最新的ready的值(這里的中間刷新的時(shí)間間隔到底是多長(zhǎng),或者子線程的刷新機(jī)制,自己也不太清楚。當(dāng)開啟一個(gè)線程去執(zhí)行時(shí),ready值改變時(shí)就會(huì)立刻刷新,循環(huán)立刻就結(jié)束,但是當(dāng)開啟多個(gè)線程時(shí),就會(huì)有一定的延遲)。
?
public class SelfTest {private static boolean ready;private static int number;private static long time;public static class ReadThread extends Thread {public void run() {while(!ready ){System. out.println("******* "+Thread.currentThread()+""+number);Thread. yield();}System. out.println(number+" currentThread: "+Thread.currentThread());}}public static void main(String [] args) {time = System.currentTimeMillis();new ReadThread().start();new ReadThread().start();new ReadThread().start();new ReadThread().start();try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}number = 42;ready = true ;System.out.println("賦值時(shí)間:ready = true ");} }?
上面這段代碼的執(zhí)行結(jié)果:可以看出賦值后,循環(huán)還是執(zhí)行了幾次。
此時(shí)如果把 ready的屬性加上 volatile 結(jié)果便是如下的效果:
由此可見Volatile可以解決內(nèi)存可見性的問(wèn)題。
上面講的加鎖機(jī)制同樣可以解決內(nèi)存可見性的問(wèn)題,加鎖的含義不僅僅局限于互斥行為,還包括內(nèi)存可見性。為了確保所有線程都能看到共享變量的最新值,所有執(zhí)行讀操作或者寫操作的線程都必須在同一個(gè)鎖上同步。
注:由于System.out.println的執(zhí)行仍然需要時(shí)間,所以這面打印的順序還是可能出現(xiàn)錯(cuò)亂。
參考:
http://www.mamicode.com/info-detail-245652.html
并發(fā)編程實(shí)戰(zhàn)
http://www.cnblogs.com/NeilZhang/p/7979629.html
夢(mèng)想不是浮躁,而是沉淀和積累
總結(jié)
以上是生活随笔為你收集整理的Java 并发基础——线程安全性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 投资者注意了,这个20万亿的理财市场,正
- 下一篇: java 子类继承父类_关于Java 的