線程安全性詳解(原子性、可見性、有序性)
本文轉(zhuǎn)載自微信公眾號(hào)「牧小農(nóng)」,作者牧小農(nóng)。轉(zhuǎn)載本文請(qǐng)聯(lián)系牧小農(nóng)公眾號(hào)。
一、定義:什么是線程安全性
當(dāng)多個(gè)線程訪問某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用 何種調(diào)度方式 或者這些進(jìn)程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個(gè)類都能表現(xiàn)出正確的行為,那么就稱這個(gè)類就是線程安全的。
二、線程安全性的三個(gè)體現(xiàn)
原子性:提供互斥訪問,同一時(shí)刻只能有一個(gè)線程對(duì)數(shù)據(jù)進(jìn)行操作(Atomic、CAS算法、synchronized、Lock)
可見性:一個(gè)主內(nèi)存的線程如果進(jìn)行了修改,可以及時(shí)被其他線程觀察到(synchronized、volatile)
有序性:如果兩個(gè)線程不能從 happens-before原則 觀察出來(lái),那么就不能觀察他們的有序性,虛擬機(jī)可以隨意的對(duì)他們進(jìn)行重排序,導(dǎo)致其觀察觀察結(jié)果雜亂無(wú)序(happens-before原則)
三、線程安全性:原子性
3.1、原子性 — Atomic包
在Java jdk中里面提供了很多Atomic類
- AtomicXXX:CAS、Unsafe.compareAndSwapInt
- AtomicLong、LongAdder
- AtomicReference、AtomicReferenceFieldUpdater
- AtomicStampReference:CAS的ABA問題
由于CAS原語(yǔ)的直接操作與計(jì)算機(jī)底層的聯(lián)系很大,CAS原語(yǔ)有三個(gè)參數(shù), 內(nèi)存地址、 期望值、 新值。我們?cè)贘ava中一般不去直接寫CAS相關(guān)的代碼,JDK為我們封裝在AtomicXXX中,因此,我們直接使用就可以了。
我們?cè)?java.util.concurrent.atomic目錄中可以看到我們這些類,包下提供了 AtomicBoolean、 AtomicLong、 AtomicInteger三種原子更新基本類型和一個(gè)比較好玩的類 AtomicReference,這些類都有一個(gè)共同點(diǎn),都支持CAS,以 AtomicInteger為重點(diǎn)講解。
3.1.1、AtomicInteger
AtomicInteger是一個(gè)提供原子操作的Integer類,通過線程安全的方式操作加減
以下是 AtomicIntege基本包括的方法:
- public final int getAndSet(int newValue) //給AtomicInteger設(shè)置newValue并返回加oldValue
- public final boolean compareAndSet(int expect, int update) //如果輸入的值和期望值相等就set并返回true/false
- public final int getAndIncrement() //對(duì)AtomicInteger原子的加1并返回當(dāng)前自增前的value
- public final int getAndDecrement() //對(duì)AtomicInteger原子的減1并返回自減之前的的value
- public final int getAndAdd(int delta) //對(duì)AtomicInteger原子的加上delta值并返加之前的value
- public final int incrementAndGet() //對(duì)AtomicInteger原子的加1并返回加1后的值
- public final int decrementAndGet() //對(duì)AtomicInteger原子的減1并返回減1后的值
- public final int addAndGet(int delta) //給AtomicInteger原子的加上指定的delta值并返回加后的值
示例:
- import lombok.extern.slf4j.Slf4j;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Semaphore;
- import java.util.concurrent.atomic.AtomicInteger;
- @Slf4j
- public class AtomicIntegerExample {
- // 請(qǐng)求總數(shù)
- public static int clientTotal = 5000;
- // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
- public static int threadTotal = 200;
- public static AtomicInteger count = new AtomicInteger(0);
- public static void main(String[] args) throws Exception {
- //獲取線程池
- ExecutorService executorService = Executors.newCachedThreadPool();
- //定義信號(hào)量
- final Semaphore semaphore = new Semaphore(threadTotal);
- final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
- for (int i = 0; i < clientTotal ; i++) {
- executorService.execute(() -> {
- try {
- semaphore.acquire();
- add();
- semaphore.release();
- } catch (Exception e) {
- log.error("exception", e);
- }
- countDownLatch.countDown();
- });
- }
- countDownLatch.await();
- executorService.shutdown();
- log.info("count:{}", count.get());
- }
- private static void add() {
- count.incrementAndGet();
- }
- }
這里我們使用請(qǐng)求總數(shù)為:5000,同時(shí)執(zhí)行的并發(fā)線程數(shù)為:200,我們最終需要得到結(jié)果為:5000,這個(gè)執(zhí)行結(jié)果才算正確。
查看返回結(jié)果:
- 13:43:26.473 [main] INFO com.mmall.concurrency.example.atomic.AtomicIntegerExample - count:5000
最后結(jié)果是 5000表示是線程安全的。
我們來(lái)看看 AtomicInteger底層代碼中到底為我們做了什么?首先我們來(lái)看 AtomicInteger.incrementAndGet() 方法
- public class AtomicInteger extends Number implements java.io.Serializable{
- /**
- * 對(duì)AtomicInteger原子的加1并返回加1后的值
- * @return 更新的值
- */
- public final int incrementAndGet() {
- return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
- }
- }
AtomicInteger 調(diào)用了java底層的unsafe.getAndAddInt()```方法,這里是實(shí)現(xiàn)CAS 的關(guān)鍵。
incrementAndGet()是將自增后的值返回,還有一個(gè)方法 getAndIncrement()是將自增前的值返回,分別對(duì)應(yīng) ++i和 i++操作。同樣的 decrementAndGet()和 getAndDecrement()則對(duì) --i和i--操作。
- Unsafe類是在sun.misc包下,不屬于Java標(biāo)準(zhǔn)。但是很多Java的基礎(chǔ)類庫(kù),包括一些被廣泛使用的高性能開發(fā)庫(kù)都是基于
- Unsafe類開發(fā)的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe類在提升Java運(yùn)行效率,增強(qiáng)Java語(yǔ)言底層操作
- 能力方面起了很大的作用。Unsafe類使Java擁有了像C語(yǔ)言的指針一樣操作內(nèi)存空間的能力,同時(shí)也帶來(lái)了指針的問題。
- 過度的使用Unsafe類會(huì)使得出錯(cuò)的幾率變大,因此Java官方并不建議使用的,官方文檔也幾乎沒有。通常我們最好也不
- 要使用Unsafe類,除非有明確的目的,并且也要對(duì)它有深入的了解才行。
再來(lái)看 Unsafe.getAndAddInt()方法
- /*
- * 其中g(shù)etIntVolatile和compareAndSwapInt都是native方法
- * getIntVolatile是獲取當(dāng)前的期望值
- * compareAndSwapInt就是我們平時(shí)說(shuō)的CAS(compare and swap),通過比較如果內(nèi)存區(qū)的值沒有改變,那么就用新值直接給該內(nèi)存區(qū)賦值
- */
- public final int getAndAddInt(Object var1, long var2, int var4) {
- int var5;
- do {
- var5 = this.getIntVolatile(var1, var2);
- } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
- return var5;
- }
- public native int getIntVolatile(Object var1, long var2);
- public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
我們可以看到 getAndAddInt(Objectvar1,longvar2,intvar4),傳進(jìn)來(lái)的第一個(gè)參數(shù)是當(dāng)前的一個(gè)對(duì)象,也就是我們的:count.incrementAndGet(),在 getAndAddInt()中,var1就是count,var2就是當(dāng)前的值,比如當(dāng)前循環(huán)中count值為 2,var4為每次遞增1
其次 getAndAddInt()方法中涉及到的兩個(gè)方法調(diào)用都定義為native,即java底層實(shí)現(xiàn)的本地方法
- getIntVolatile():獲取保存當(dāng)前對(duì)象count的主存地址的引用(注意不是對(duì)象的值,是引用)。
- compareAndSwapInt():比較當(dāng)前對(duì)象的值和底層該對(duì)象的值是否相等,如果相等,則將當(dāng)前對(duì)象值加1,如果不相等,則重新去獲取底層該對(duì)象的值,這個(gè)方法的實(shí)現(xiàn)就是CPU的CAS(compare and swap)操作。
我們知道 volatile具有一致性的特征,但是它不具備原子性,為什么 AtomicInteger卻同時(shí)具備一致性和原子性,原來(lái)在 AtomicInteger源碼中實(shí)現(xiàn)了這樣一串代碼:privatevolatileintvalue;,在 AtomicInteger內(nèi)部實(shí)現(xiàn)就使用了 volatile關(guān)鍵字,這就是為什么執(zhí)行CAS(對(duì)CAS有疑問的點(diǎn)擊此處)操作的時(shí)候,從底層獲取的數(shù)據(jù)就是最新的數(shù)據(jù):
如果當(dāng)前要保存的值和內(nèi)存中最新的值不相等的話,說(shuō)明在這個(gè)過程中被其他線程修改了,只
能獲取更新當(dāng)前值為最新值,再那這個(gè)當(dāng)前值再去和重新去內(nèi)存獲取的最新值比較,直到二者
相等的時(shí)候,才完成+1的過程.
使用 AtomicInteger的好處在于,它不同于 sychronized關(guān)鍵字或 lock用鎖的形式來(lái)實(shí)現(xiàn)原子性,加鎖會(huì)影響性能,而是采用循環(huán)比較的形式來(lái)提高性能。
3.1.2、AtomicLong
AtomicLong 是作用是對(duì)長(zhǎng)整形進(jìn)行原子操作,依靠底層的cas來(lái)保障原子性的更新數(shù)據(jù),在要添加或者減少的時(shí)候,會(huì)使用死循環(huán)不斷地cas到特定的值,從而達(dá)到更新數(shù)據(jù)的目的。
- import lombok.extern.slf4j.Slf4j;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Semaphore;
- import java.util.concurrent.atomic.AtomicLong;
- @Slf4j
- public class AtomicLongExample {
- // 請(qǐng)求總數(shù)
- public static int clientTotal = 5000;
- // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
- public static int threadTotal = 200;
- public static AtomicLong count = new AtomicLong(0);
- public static void main(String[] args) throws Exception {
- ExecutorService executorService = Executors.newCachedThreadPool();
- final Semaphore semaphore = new Semaphore(threadTotal);
- final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
- for (int i = 0; i < clientTotal ; i++) {
- executorService.execute(() -> {
- try {
- semaphore.acquire();
- add();
- semaphore.release();
- } catch (Exception e) {
- log.error("exception", e);
- }
- countDownLatch.countDown();
- });
- }
- countDownLatch.await();
- executorService.shutdown();
- log.info("count:{}", count.get());
- }
- private static void add() {
- count.incrementAndGet();
- // count.getAndIncrement();
- }
- }
執(zhí)行結(jié)果:
- 14:59:38.978 [main] INFO com.mmall.concurrency.example.atomic.AtomicLongExample - count:5000
最后結(jié)果是 5000表示是線程安全的。
3.1.3、AtomicBoolean
AtomicBoolean位于java.util.concurrent.atomic包下,是java提供給的可以保證數(shù)據(jù)的原子性操作的一個(gè)類
- import lombok.extern.slf4j.Slf4j;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Semaphore;
- import java.util.concurrent.atomic.AtomicBoolean;
- @Slf4j
- public class AtomicBooleanExample {
- private static AtomicBoolean isHappened = new AtomicBoolean(false);
- // 請(qǐng)求總數(shù)
- public static int clientTotal = 5000;
- // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
- public static int threadTotal = 200;
- public static void main(String[] args) throws Exception {
- ExecutorService executorService = Executors.newCachedThreadPool();
- final Semaphore semaphore = new Semaphore(threadTotal);
- final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
- for (int i = 0; i < clientTotal ; i++) {
- executorService.execute(() -> {
- try {
- semaphore.acquire();
- test();
- semaphore.release();
- } catch (Exception e) {
- log.error("exception", e);
- }
- countDownLatch.countDown();
- });
- }
- countDownLatch.await();
- executorService.shutdown();
- log.info("isHappened:{}", isHappened.get());
- }
- private static void test() {
- if (isHappened.compareAndSet(false, true)) {
- log.info("execute");
- }
- }
- }
返回結(jié)果:
- 15:04:54.954 [pool-1-thread-2] INFO com.mmall.concurrency.example.atomic.AtomicBooleanExample - execute
- 15:04:54.971 [main] INFO com.mmall.concurrency.example.atomic.AtomicBooleanExample - isHappened:true
這里我們發(fā)現(xiàn) log.info("execute");,在代碼中只執(zhí)行了一次,并且 isHappened:true的值為true,這是為啥呢?
這里是因?yàn)楫?dāng)程序第一次 compareAndSet()的時(shí)候,使得 isHappend變?yōu)榱藅rue,因?yàn)樵有缘年P(guān)系,沒有其他線程進(jìn)行干擾,通過使用AtomicBoolean,我們使某段代碼只執(zhí)行一次。
3.1.4、AtomicReference
AtomicReference和 AtomicInteger非常類似,不同之處就在于 AtomicInteger是對(duì)整數(shù)的封裝,底層采用的是 compareAndSwapInt實(shí)現(xiàn)CAS,比較的是數(shù)值是否相等,而 AtomicReference則對(duì)應(yīng)普通的對(duì)象引用,底層使用的是 compareAndSwapObject實(shí)現(xiàn)CAS,比較的是兩個(gè)對(duì)象的地址是否相等。也就是它可以保證你在修改對(duì)象引用時(shí)的線程安全性。
多個(gè)線程之間的操作無(wú)論采用何種執(zhí)行時(shí)序或交替方式,都要保證不變性條件不被破壞,要保持狀態(tài)的一致性,就需要在單個(gè)原子操作中更新相關(guān)的狀態(tài)變量。
- import lombok.extern.slf4j.Slf4j;
- import java.util.concurrent.atomic.AtomicReference;
- @Slf4j
- public class AtomicReferenceExample {
- private static AtomicReference<Integer> count = new AtomicReference<>(0);
- public static void main(String[] args) {
- count.compareAndSet(0, 2);
- count.compareAndSet(0, 1);
- count.compareAndSet(1, 3);
- count.compareAndSet(2, 4);
- count.compareAndSet(3, 5);
- log.info("count:{}", count.get());
- }
- }
大家覺得我們輸出的結(jié)果會(huì)是多少?
返回結(jié)果:
- 15:26:59.680 [main] INFO com.mmall.concurrency.example.atomic.AtomicReferenceExample - count:4
為什么是4呢?首先我們 要說(shuō)的是 publicfinalbooleancompareAndSet(V expect,V update)這個(gè)方法,這個(gè)方法主要的作用是通過比對(duì)兩個(gè)對(duì)象,然后更新為新的對(duì)象,這里的比對(duì)兩個(gè)對(duì)象,比對(duì)的方式不是 equals而是 ==,意味著比對(duì)的是內(nèi)存的中地址。
1、首先我們創(chuàng)建 count的初始化為0 2、在main方法中 count.compareAndSet(0,2);,判斷count為0時(shí)賦值為2 3、在 count.compareAndSet(0,1);和 count.compareAndSet(1,3);判斷count是否為1或者0,因?yàn)樯弦徊轿覀円呀?jīng)賦值為2了,所以判斷不成立 4、在 count.compareAndSet(2,4);判斷count是否為2,等式成立 5、最好輸出結(jié)果為4
- count.compareAndSet(0, 2); //count=0?賦值 2,判斷成立,此時(shí)count=0,更新后為2
- count.compareAndSet(0, 1); //count=0?賦值 1,判斷不成立,此時(shí)count=2
- count.compareAndSet(1, 3); //count=1?賦值 3,判斷不成立,此時(shí)count=2
- count.compareAndSet(2, 4); //count=2?賦值 4,判斷成立,此時(shí)count=2,更新后count=4
- count.compareAndSet(3, 5); //count=3?賦值 5,判斷不成立,此時(shí)count=4
所以我們輸出結(jié)果為:4
3.1.5、CAS中ABA問題的解決
CAS并非完美的,它會(huì)導(dǎo)致ABA問題,例如:當(dāng)前內(nèi)存的值一開始是A,被另外一個(gè)線程先改為B然后再改為A,那么當(dāng)前線程訪問的時(shí)候發(fā)現(xiàn)是A,則認(rèn)為它沒有被其他線程訪問過。在某些場(chǎng)景下這樣是存在錯(cuò)誤風(fēng)險(xiǎn)的。比如在鏈表中。如何解決這個(gè)ABA問題呢,大多數(shù)情況下樂觀鎖的實(shí)現(xiàn)都會(huì)通過引入一個(gè)版本號(hào)標(biāo)記這個(gè)對(duì)象,每次修改版本號(hào)都會(huì)變?cè)?,比如使用時(shí)間戳作為版本號(hào),這樣就可以很好的解決ABA問題。在JDK中提供了 AtomicStampedReference類來(lái)解決這個(gè)問題,這個(gè)類維護(hù)了一個(gè)int類型的標(biāo)記stamp,每次更新數(shù)據(jù)的時(shí)候順帶更新一下stamp。
3.2、原子性 — synchronized
synchronized是一種同步鎖,通過鎖實(shí)現(xiàn)原子操作。1、修飾代碼塊:大括號(hào)括起來(lái)的代碼,作用于調(diào)用的對(duì)象 2、修飾方法:整個(gè)方法,作用于調(diào)用的對(duì)象 3、修飾靜態(tài)方法:整個(gè)靜態(tài)方法,作用于所有對(duì)象 4、修飾類:括號(hào)括起來(lái)的部分,作用于所有對(duì)象
詳細(xì)可以查看,我寫的關(guān)于:synchronized的博客,因?yàn)閷戇^所以就不做過多描述。
3.3、原子性 — 對(duì)比
Atomic:競(jìng)爭(zhēng)激烈時(shí)能維持常態(tài),比Lock性能好, 只能同步一個(gè)值
synchronized:不可中斷鎖,適合競(jìng)爭(zhēng)不激烈,可讀性好的情況
Lock:可中斷鎖,多樣化同步,競(jìng)爭(zhēng)激烈時(shí)能維持常態(tài)
四、線程安全性:可見性
簡(jiǎn)介:一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)被其他線程觀察到
導(dǎo)致共享變量在線程間不可見的原因:1.線程交叉執(zhí)行 2.重新排序結(jié)合線程交叉執(zhí)行 3.共享變量更新后的值沒有在工作內(nèi)存中與主內(nèi)存間及時(shí)更新
4.1 可見性 — syncronized
JMM關(guān)于 syncronized的兩條規(guī)定:
線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存中
線程加鎖時(shí),將清空工作內(nèi)存中共享變量的值,從而使得使用共享變量時(shí)需要從主內(nèi)存中重新讀取最新的值(注意:加鎖與解鎖是同一把鎖) 由于syncronized可以保證原子性及可見性,變量只要被syncronized修飾,就可以放心的使用
4.2 可見性 — volatile
通過加入 內(nèi)存屏障和 禁止重排序優(yōu)化來(lái)實(shí)現(xiàn)可見性。具體實(shí)現(xiàn)過程:
- 對(duì) volatile變量寫操作時(shí),會(huì)在寫操作后加入一條 store屏障指令,將本地內(nèi)存中的共享變量值刷新到主內(nèi)存
- 對(duì) volatile變量讀操作時(shí),會(huì)在讀操作前加入一條 load屏障指令,從主內(nèi)存中讀取共享變量
- volatile 不能保證操作的原子性,也就是不能保證線程安全性, 如果需要使用 volatile 必須滿足以下兩個(gè)條件:
- 對(duì)變量的寫操作不依賴與變量當(dāng)前的值。
- 該變量沒有包含在具有其他變量的不變的式子中。
所以 volatile修飾的變量適合作為狀態(tài)標(biāo)記量。
注:以下圖片為資料中獲取,如有雷同,純屬巧合
示例:
- import lombok.extern.slf4j.Slf4j;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Semaphore;
- @Slf4j
- public class VolatileExample {
- // 請(qǐng)求總數(shù)
- public static int clientTotal = 5000;
- // 同時(shí)并發(fā)執(zhí)行的線程數(shù)
- public static int threadTotal = 200;
- public static volatile int count = 0;
- public static void main(String[] args) throws Exception {
- ExecutorService executorService = Executors.newCachedThreadPool();
- final Semaphore semaphore = new Semaphore(threadTotal);
- final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
- for (int i = 0; i < clientTotal ; i++) {
- executorService.execute(() -> {
- try {
- semaphore.acquire();
- add();
- semaphore.release();
- } catch (Exception e) {
- log.error("exception", e);
- }
- countDownLatch.countDown();
- });
- }
- countDownLatch.await();
- executorService.shutdown();
- log.info("count:{}", count);
- }
- private static void add() {
- count++;
- }
- }
返回結(jié)果:
- 16:12:01.404 [main] INFO com.mmall.concurrency.example.count.VolatileExample4 - count:4986
通過執(zhí)行代碼我們可以發(fā)現(xiàn),返回結(jié)果并不是我們想看到的5000,說(shuō)明這個(gè)是線程不安全的類
主要是因?yàn)楫?dāng)我們執(zhí)行 conut++時(shí)分成了三步:1、取出當(dāng)前內(nèi)存count值,這時(shí)count值時(shí)最新的 2、+1操作 3、重新寫回主存
例如:有兩個(gè)線程同時(shí)在執(zhí)行 count++,兩個(gè)內(nèi)存都執(zhí)行了第一步,比如當(dāng)前count值為99,它們都讀到了這個(gè)count值,然后兩個(gè)線程分別執(zhí)行了 +1,并寫回主存,這樣就丟掉了一次 +1的操作。
五、線程安全性:有序性
在JMM中,允許編譯器和處理器對(duì)指令進(jìn)行重排序,但是重排序過程不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性。
通過volatile、synchronized、lock保證有序性
5.1 happens-before原則
- 程序次序規(guī)則:一個(gè)線程內(nèi),按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作
- 鎖定規(guī)則:一個(gè) unLock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的 Lock()操作,也就是說(shuō)只有先解鎖才能對(duì)下面的線程進(jìn)行加鎖
- volatile變量規(guī)則:對(duì)一個(gè)變量的寫操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作
- 傳遞規(guī)則:如果操作A先行發(fā)生與操作B,而操作B先行發(fā)生于操作C,則操作A先行發(fā)生于操作C
- 線程啟動(dòng)規(guī)則: Thread對(duì)象的 start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作,一個(gè)線程只有執(zhí)行了 start()方法后才能做其他的操作
- 線程終端規(guī)則:對(duì)線程 interrupt()方法的調(diào)用先行發(fā)生與被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生(只有執(zhí)行了 interrupt()方法才可以檢測(cè)到中斷事件的發(fā)生)
- 線程終結(jié)規(guī)則:線程中所有操作都先行發(fā)生于線程的終止檢測(cè),我們可以通過 Thread.join()方法結(jié)束, Thread.isAlive()的返回值手段檢測(cè)到線程已經(jīng)終止執(zhí)行
- 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于他的 finalize()方法的開始
六、線程安全性:總結(jié)
- 原子性:Atomic包、CAS算法、synchronized、Lock
- 可見性:synchronized、volatile
一個(gè)主內(nèi)存的線程如果進(jìn)行了修改,可以及時(shí)被其他線程觀察到,介紹了volatile如何被觀察到的
- 有序性:happens-before原則 happens-before原則,觀察結(jié)果,如果兩個(gè)線程不能偶從happens-before原則觀察出來(lái),那么就不能觀察他們的有序性,虛擬機(jī)可以隨意的對(duì)他們進(jìn)行重排序