一帜羊、原子性
????原子性操作指相應(yīng)的操作是單一不可分割的操作咒程。在我們學(xué)化學(xué)這門課程的時候,對于里面講到的原子性相信大家都非常明白,原子是微觀世界中最小的不可再進行分割的單元,原子是最小的粒子忘伞。java里面的原子性操作也是如此,它代表著一個操作不能再進行分割是最小的執(zhí)行單元饥瓷,或者一系列操作要么全部成功執(zhí)行,要么全部執(zhí)行失敗痹籍,不允許中間某一些成功失敗呢铆,類比如事物控制,要么全部提交要么全部回滾蹲缠。 ????下面根據(jù)幾個粒子來分析下原子性操作:
i?=?0;???????//1 j?=?i?;??????//2 i++;?????????//3 i?=?j?+?1;???//4
上面四個操作棺克,有哪個幾個是原子操作悠垛,那幾個不是?如果不是很理解娜谊,可能會認為都是原子性操作确买,其實只有1才是原子操作,其余均不是因俐。
1在Java中拇惋,對基本數(shù)據(jù)類型的變量和賦值操作都是原子性操作周偎;? 2中包含了兩個操作:讀取i抹剩,將i值賦值給j? 3中包含了三個操作:讀取i值、i?+?1?蓉坎、將+1結(jié)果賦值給i澳眷;? 4中同三一樣
在單線程環(huán)境下我們可以認為整個步驟都是原子性操作,但是在多線程環(huán)境下則不同蛉艾,Java只保證了基本數(shù)據(jù)類型的變量和賦值操作才是原子性的(注:在32位的JDK環(huán)境下钳踊,對64位數(shù)據(jù)的讀取不是原子性操作*,如long勿侯、double)拓瞪。在多線程環(huán)境中,非原子操作可能會受其他線程的干擾助琐,例如第3個操作祭埂,i在加1之后將結(jié)果賦值給i,在賦值給i回寫主內(nèi)存的時候可能會被其他線程搶先回寫兵钮,導(dǎo)致此次執(zhí)行失敗丟失了本次計算結(jié)果(這里會涉及到原子性操作蛆橡,下面會進行講解)。
public?class?AtomicTest?{ ????private?int?i?=?0; ????public?void?add()?{ ????????i++; ????} ????public?static?void?main(String[]?args)?{???????? ????????for?(int?t?=?0;?t?<?10;?t++)?{ ????????????AtomicTest?test?=?new?AtomicTest(); ????????????Thread[]?threads?=?new?Thread[10];??????????? ????????????for?(int?i?=?0;?i?<?threads.length;?i++)?{ ????????????????threads[i]?=?new?Thread(()?->?{???????????????????? ????????????????for?(int?k?=?0;?k?<?1000;?k++)?{ ????????????????????????test.add(); ????????????????????} ????????????????});??????????????? ????????????????threads[i].start(); ????????????}???????????? ????????????Arrays.stream(threads).forEach(th?->?{???????????????? ????????????try?{ ????????????????????th.join(); ????????????????}?catch?(InterruptedException?e)?{ ????????????????????e.printStackTrace(); ????????????????} ????????????});???????????? ????????????System.out.println("第"?+?(t?+?1)?+?"次執(zhí)行結(jié)果:"?+?test.i); ????????} ????} }
第1次執(zhí)行結(jié)果:8987第2次執(zhí)行結(jié)果:8970第3次執(zhí)行結(jié)果:6820第4次執(zhí)行結(jié)果:9841第5次執(zhí)行結(jié)果:10000第6次執(zhí)行結(jié)果:7766第7次執(zhí)行結(jié)果:8105第8次執(zhí)行結(jié)果:10000第9次執(zhí)行結(jié)果:10000第10次執(zhí)行結(jié)果:10000
最終的執(zhí)行結(jié)果會是小于等于10000掘譬,在某些情況下與我們所期望的結(jié)果10000不符合泰演,并發(fā)的情況下導(dǎo)致bug的產(chǎn)生。
要想在多線程環(huán)境下保證原子性葱轩,則可以通過鎖睦焕、synchronized來確保。volatile是無法保證復(fù)合操作的原子性靴拱。
二垃喊、可見性
int?i?=?0;????????????//語句1?? boolean?flag?=?false;?//語句2 i?=?1;????????????????//語句3?? flag?=?true;??????????//語句4
public?class?SerialTest?{ ????static?SerialTest?serialTest; ????static?boolean?isInit?=?false; ????public?static?void?main(String[]?args)?{???????? ????for(int?i=0;?i<?200;i++)?{ ????????????serialTest?=?null; ????????????isInit?=?false;???????????? ????????????new?Thread(()->{ ????????????????serialTest?=?new?SerialTest();//語句1 ????????????????isInit?=?true;????????????????//語句2 ????????????}).start();???????????? ????????????new?Thread(()->{???????????????? ????????????????if(isInit)?{ ????????????????????serialTest.doSomething(); ????????????????} ????????????}).start(); ????????} ????}???? ????public?void?doSomething()?{???????? ????????System.out.println("doSomething"); ????} }
Exception?in?thread?"Thread-283"?java.lang.NullPointerException at?com.cd.concurrent.SerialTest.lambda$main$1(SerialTest.java:25) at?java.lang.Thread.run(Thread.java:748) ...... doSomething doSomething doSomething doSomething doSomething Exception?in?thread?"Thread-283"?java.lang.NullPointerException at?com.cd.concurrent.SerialTest.lambda$main$1(SerialTest.java:25) at?java.lang.Thread.run(Thread.java:748)