Java 多线程三大核心
原子性
Java?的原子性就和數(shù)據(jù)庫(kù)事務(wù)的原子性差不多,一個(gè)操作中要么全部執(zhí)行成功或者失敗。
JMM?只是保證了基本的原子性,但類似于?i++?之類的操作,看似是原子操作,其實(shí)里面涉及到:
- 獲取 i 的值。
- 自增。
- 再賦值給 i。
這三步操作,所以想要實(shí)現(xiàn)?i++?這樣的原子操作就需要用到?synchronized?或者是?lock?進(jìn)行加鎖處理。
如果是基礎(chǔ)類的自增操作可以使用?AtomicInteger?這樣的原子類來(lái)實(shí)現(xiàn)(其本質(zhì)是利用了?CPU?級(jí)別的 的?CAS?指令來(lái)完成的)。
其中用的最多的方法就是:?incrementAndGet()?以原子的方式自增。 源碼如下:
public final long incrementAndGet() {for (;;) {long current = get();long next = current + 1;if (compareAndSet(current, next))return next;}}首先是獲得當(dāng)前的值,然后自增 +1。接著則是最核心的?compareAndSet()?來(lái)進(jìn)行原子更新。
public final boolean compareAndSet(long expect, long update) {return unsafe.compareAndSwapLong(this, valueOffset, expect, update);}其邏輯就是判斷當(dāng)前的值是否被更新過,是否等于?current,如果等于就說(shuō)明沒有更新過然后將當(dāng)前的值更新為?next,如果不等于則返回false?進(jìn)入循環(huán),直到更新成功為止。
還有其中的?get()?方法也很關(guān)鍵,返回的是當(dāng)前的值,當(dāng)前值用了?volatile?關(guān)鍵詞修飾,保證了內(nèi)存可見性。
private volatile int value;可見性
現(xiàn)代計(jì)算機(jī)中,由于?CPU?直接從主內(nèi)存中讀取數(shù)據(jù)的效率不高,所以都會(huì)對(duì)應(yīng)的?CPU?高速緩存,先將主內(nèi)存中的數(shù)據(jù)讀取到緩存中,線程修改數(shù)據(jù)之后首先更新到緩存,之后才會(huì)更新到主內(nèi)存。如果此時(shí)還沒有將數(shù)據(jù)更新到主內(nèi)存其他的線程此時(shí)來(lái)讀取就是修改之前的數(shù)據(jù)。
如上圖所示。
volatile?關(guān)鍵字就是用于保證內(nèi)存可見性,當(dāng)線程A更新了 volatile 修飾的變量時(shí),它會(huì)立即刷新到主線程,并且將其余緩存中該變量的值清空,導(dǎo)致其余線程只能去主內(nèi)存讀取最新值。
使用?volatile?關(guān)鍵詞修飾的變量每次讀取都會(huì)得到最新的數(shù)據(jù),不管哪個(gè)線程對(duì)這個(gè)變量的修改都會(huì)立即刷新到主內(nèi)存。
synchronized和加鎖也能能保證可見性,實(shí)現(xiàn)原理就是在釋放鎖之前其余線程是訪問不到這個(gè)共享變量的。但是和?volatile相比開銷較大。
順序性
以下這段代碼:
int a = 100 ; //1 int b = 200 ; //2 int c = a + b ; //3正常情況下的執(zhí)行順序應(yīng)該是?1>>2>>3。但是有時(shí)?JVM?為了提高整體的效率會(huì)進(jìn)行指令重排導(dǎo)致執(zhí)行的順序可能是?2>>1>>3。但是?JVM?也不能是什么都進(jìn)行重排,是在保證最終結(jié)果和代碼順序執(zhí)行結(jié)果一致的情況下才可能進(jìn)行重排。
重排在單線程中不會(huì)出現(xiàn)問題,但在多線程中會(huì)出現(xiàn)數(shù)據(jù)不一致的問題。
Java 中可以使用?volatile?來(lái)保證順序性,synchronized 和 lock?也可以來(lái)保證有序性,和保證原子性的方式一樣,通過同一段時(shí)間只能一個(gè)線程訪問來(lái)實(shí)現(xiàn)的。
除了通過?volatile?關(guān)鍵字顯式的保證順序之外,?JVM?還通過?happen-before?原則來(lái)隱式的保證順序性。
其中有一條就是適用于?volatile?關(guān)鍵字的,針對(duì)于?volatile?關(guān)鍵字的寫操作肯定是在讀操作之前,也就是說(shuō)讀取的值肯定是最新的。
volatile 的應(yīng)用
雙重檢查鎖的單例模式
可以用?volatile?實(shí)現(xiàn)一個(gè)雙重檢查鎖的單例模式:
public class Singleton {private static volatile Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}這里的?volatile?關(guān)鍵字主要是為了防止指令重排。 如果不用?volatile?,singleton = new Singleton();,這段代碼其實(shí)是分為三步:
- 分配內(nèi)存空間。(1)
- 初始化對(duì)象。(2)
- 將?singleton?對(duì)象指向分配的內(nèi)存地址。(3)
加上?volatile?是為了讓以上的三步操作順序執(zhí)行,反之有可能第三步在第二步之前被執(zhí)行就有可能導(dǎo)致某個(gè)線程拿到的單例對(duì)象還沒有初始化,以致于使用報(bào)錯(cuò)。
控制停止線程的標(biāo)記
private volatile boolean flag ;private void run(){new Thread(new Runnable() {@Overridepublic void run() {doSomeThing();}});}private void stop(){flag = false ;}這里如果沒有用 volatile 來(lái)修飾 flag ,就有可能其中一個(gè)線程調(diào)用了?stop()方法修改了 flag 的值并不會(huì)立即刷新到主內(nèi)存中,導(dǎo)致這個(gè)循環(huán)并不會(huì)立即停止。
這里主要利用的是?volatile?的內(nèi)存可見性。
總結(jié)一下:
- volatile?關(guān)鍵字只能保證可見性,順序性,不能保證原子性。
總結(jié)
以上是生活随笔為你收集整理的Java 多线程三大核心的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: synchronized 关键字原理
- 下一篇: java ReentrantLock 实