CPU高速緩存、CPU多級(jí)緩存、CPU寄存器和CPU亂序執(zhí)行優(yōu)化
由于cpu的運(yùn)算速度遠(yuǎn)遠(yuǎn)大于主內(nèi)存的工作速度,為了能讓主內(nèi)存不要太落后cpu,引入了高速緩存,隨著計(jì)算機(jī)的復(fù)雜程度的提升,高速緩存與主存的速度差異越來越大,隨后加入了多級(jí)緩存:三級(jí)緩存。由此進(jìn)一步緩解cpu與主存的速度差異。

指令重排
為了充分提升cpu性能,cup可以對(duì)代碼的亂序執(zhí)行,在單核單線程情況下這種這種機(jī)制能保證運(yùn)算結(jié)果與代碼順序執(zhí)行后的結(jié)果一致,但是多線程情況下就會(huì)出現(xiàn)結(jié)果不一致的情況。這個(gè)也稱作cpu的指令重排
JVM堆棧和計(jì)算機(jī)結(jié)構(gòu)的關(guān)系
存在堆上的對(duì)象可以被棧上的持有這個(gè)對(duì)象引用的方法所訪問,如果棧上兩個(gè)方法都擁有了這個(gè)對(duì)象的引用,可能會(huì)發(fā)生并發(fā)問題,因?yàn)闂I系木€程擁有了這個(gè)對(duì)象的私有拷貝

JMM與并發(fā):
主存:計(jì)算機(jī)主內(nèi)存,堆棧主要就是存在主內(nèi)存中,堆棧內(nèi)存屬于主內(nèi)存的一部分

由于線程是將工作內(nèi)存的共享變量拷貝到工作內(nèi)存進(jìn)行處理,每個(gè)線程的工作內(nèi)存都是線程私有的,所以線程之間的操作是互相不可見的,因此會(huì)產(chǎn)生線程并發(fā)問題
JMM同步的八大操作:
read:是將主內(nèi)存的變量讀取出來
locad:將主內(nèi)存read到的變量加載進(jìn)工作內(nèi)存中
use:將工作內(nèi)存的變量傳遞給執(zhí)行引擎
assign:從執(zhí)行引擎中接收變量
store: 將assign到的變量存儲(chǔ)到工作內(nèi)存中
write: 將工作內(nèi)存中的變量寫入主內(nèi)存
lock:將主內(nèi)存的變量標(biāo)識(shí)為線程獨(dú)占
unlock:將主內(nèi)存處于lock的變量釋放。

八大操作的規(guī)則:
1、read與load不允許單獨(dú)出現(xiàn)、write與store也不允許單獨(dú)出現(xiàn),并且它們必須是按順序的操作,不允許只read不load或者只store不write,只有read完后才能load,只有store完之后才能write,但是沒有要求他們連續(xù)執(zhí)行中間是可以穿插其他的指令操作的,兩套操作具有原子性。
2、變量在工作內(nèi)存中發(fā)生變化,必須更新到主內(nèi)存中,新的變量只能在主內(nèi)存中產(chǎn)生,不允許在工作內(nèi)存中使用未被初始化的變量,就是在store和use前必須做assign和use,通過load或者assign才能拿到變量初始化的值。
3、一個(gè)變量同一時(shí)刻只允許一個(gè)線程執(zhí)行l(wèi)ock操作這個(gè)是同步性,同一個(gè)lock允許被同一條線程執(zhí)行多次,但是必須執(zhí)行相同次數(shù)的unlock變量才被解鎖,這就是可重入性。
4、變量被執(zhí)行l(wèi)ock時(shí),將清空工作內(nèi)存中此變量的值,執(zhí)行引擎使用此變量之前,需重新執(zhí)行l(wèi)oad操作,這就是可見性
5、當(dāng)對(duì)變量進(jìn)行unlock操作時(shí)之前,必須將此變量從工作內(nèi)存中同步回主內(nèi)存。
線程安全性:
阻塞同步:又稱悲觀鎖,主要是使同一時(shí)刻只有獲取鎖的線程才能進(jìn)行操作,其他線程只能阻塞,如synchronized和ReentrantLock都是阻塞同步
非阻塞同步:又稱樂觀鎖,全程不上鎖,主要是在修改數(shù)據(jù)前通過檢測(cè)是否有其他線程競爭,有則放棄修改,采取其他措施補(bǔ)償,如重試、自旋等,CAS就是非阻塞同步的
線程安全性的三點(diǎn)特性:
synchronized以及ReentrantLock都是擁有這些特性的
原子性:
提供互斥訪問,同一時(shí)刻只允許一個(gè)線程進(jìn)行對(duì)它進(jìn)行操作操作
代碼演示:
原子類AtomicIntegerFieldUpdater實(shí)現(xiàn)一個(gè)CAS
public class BingFa2 {
private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2+1);
public volatile int count=0;
private static AtomicIntegerFieldUpdater<BingFa2> updater=
AtomicIntegerFieldUpdater.newUpdater(BingFa2.class,"count");
CountDownLatch countDownLatch = new CountDownLatch(1000);
static BingFa2 bingFa2=new BingFa2();
public void test1() throws InterruptedException {
for (int i = 0; i < 1000; i++) {
executorService.submit(() -> {
int qw;
do {
//從底層拿值,期望值
qw = updater.get(bingFa2);
//加一
}while (!updater.compareAndSet(bingFa2,qw,count+1));
countDownLatch.countDown();
});
}
executorService.shutdown();
countDownLatch.await();
System.out.println(updater.get(bingFa2));
}
public static void main(String[] args) throws InterruptedException {
bingFa2.test1();
}
}

原子類AtomicReference
public class AtomicReferenceTest {
private static AtomicReference<Integer> compare =
new AtomicReference<>(0);
public void test1() throws InterruptedException {
//比較值后在賦值,賦值成功則返回true,否則false
System.out.println(compare.compareAndSet(0, 1));//true
System.out.println(compare.compareAndSet(2, 1));//false
System.out.println(compare.compareAndSet(1, 3));//true
System.out.println(compare.get());
}
public static void main(String[] args) throws InterruptedException {
AtomicReferenceTest atomicReferenceTest = new AtomicReferenceTest();
atomicReferenceTest.test1();
}
}

LongAdder的演示,LongAdder在并發(fā)高的時(shí)候可以分散熱點(diǎn)數(shù)據(jù)性能較好,但是并發(fā)很高時(shí)數(shù)據(jù)可能不一致,所以要求數(shù)據(jù)精準(zhǔn)的場(chǎng)景不要用它,但是在并發(fā)高很高的場(chǎng)景要求性能高的使用它比較好,并發(fā)很多低的還是用其他的Atomic類比較好,如AtomicLong,AtomicInteger
public class LongAddTest {
private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2+1);
private static LongAdder longAdder=new LongAdder();
CountDownLatch countDownLatch = new CountDownLatch(1000);
public void test1() throws InterruptedException {
for (int i = 0; i < 1000; i++) {
executorService.submit(() -> {
//加一
longAdder.increment();
countDownLatch.countDown();
});
}
executorService.shutdown();
countDownLatch.await();
System.out.println(longAdder.longValue());
}
public static void main(String[] args) throws InterruptedException {
LongAddTest longAddTest=new LongAddTest();
longAddTest.test1();
}
}

CAS的ABA問題:
ABA問題就是在CAS操作時(shí),其他線程將變量由A改為了B,然后又改回了A,這時(shí)CAS從主內(nèi)存中得到的變量是A值和工作內(nèi)存中的值一樣,這個(gè)結(jié)果雖然一樣,但是畢竟被其他線程修改過,和原子性思想不符,解決這個(gè)問題只需要在變量的版本號(hào)加1,每次修改變量都會(huì)修改版本號(hào),這樣就很好的解決了ABA問題
可見性:
一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)被其他線程觀察到
造成共享變量不可見的原因有三點(diǎn):
1、線程的交叉執(zhí)行 2、指令重排序加線程交叉執(zhí)行 3、工作內(nèi)存中的共享變量發(fā)生變化沒有及時(shí)更新到主內(nèi)存中?;蛘呤侵鲀?nèi)存的共享變量發(fā)生變化沒有更新值工作內(nèi)存中
可見性的實(shí)現(xiàn)手段:
synchronized:
這個(gè)其實(shí)我在上面的jmm八大操作的lock和unlock中就提到了:
解鎖:線程解鎖前必須要把共享變量的最新值刷新到主內(nèi)存中
加鎖:線程必須把工作內(nèi)存的共享變量清理,把使用到的共享變量從主內(nèi)存中更新到工作內(nèi)存中
volatile:
禁止cup的指令重排優(yōu)化,在對(duì)volatile變量寫操作時(shí),加入一個(gè)store屏障,禁止指令的重排,強(qiáng)制將變量的更新刷新到主內(nèi)存中,在對(duì)volatile變量讀操作時(shí),加入一個(gè)load屏障,禁止指令的重排,強(qiáng)制的將主內(nèi)存的變量刷新到工作內(nèi)存中,因此volatile保證了線程的可見性
線程在讀volatile變量時(shí)會(huì)將本地內(nèi)存的變量值置為無效,重新讀取主內(nèi)存的值,線程在寫volatile變量后會(huì)將本地內(nèi)存的成員變量的值刷新到主內(nèi)存中,如此實(shí)現(xiàn)了線程之間的可見性
volatile能實(shí)現(xiàn)線程對(duì)一個(gè)變量修改后的可見性,但是無法保證兩個(gè)線程同時(shí)操作一個(gè)變量的發(fā)生的不安全現(xiàn)象,volatile依舊是線程不安全的
一、volatile關(guān)鍵字的作用
1、保證變量寫操作的可見性;
2、保證變量前后代碼的執(zhí)行順序;
二、volatile的底層原理
被volatile修飾的變量被修改時(shí),會(huì)將修改后的變量直接寫入主存中,并且將其他線程中該變量的緩存置為無效,從而讓其它線程對(duì)該變量的引用直接從主存中獲取數(shù)據(jù),這樣就保證了變量的可見性。 但是volatile修飾的變量在自增時(shí)由于該操作分為讀寫兩個(gè)步驟,所以當(dāng)一個(gè)線程的讀操作被阻塞時(shí),另一個(gè)線程同時(shí)也進(jìn)行了自增操作,此時(shí)由于第一個(gè)線程的寫操作沒有進(jìn)行所以主存中仍舊是之前的原數(shù)據(jù),所以當(dāng)兩個(gè)線程自增完成后,該變量可能只加了1。因而volatile是無法保證對(duì)變量的任何操作都是原子性的。
三、volatile的適用場(chǎng)景
1、變量的修改不依賴于變量本身
像是i++、i+=這類的操作在多線程下都是不能保證變量的原子性的。
2、該變量沒有包含在具有其他變量的不變式中
happens-before:
要保證線程的可見性,則線程之間要存在happens-before原則:

有序性:
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行
多線程下指令重排序會(huì)造成線程不安全:
public class Singleton {
private static Singleton unsafeObject=null;
//線程不安全,發(fā)生了指令重排,原因是unsafeObject是不可見的
public static Singleton unsafeObject(){
if (unsafeObject==null){
synchronized (Singleton.class){
if (unsafeObject==null) {
return new Singleton();
}
}
}
return unsafeObject;
}
}
//Singleton成員變量加上volatile禁止指令重排序,就解決了上面代碼的線程不安全問題
private static volatile Singleton unsafeObject=null;
synchronized的底層原理:
https://juejin.cn/post/6973571891915128846
對(duì)象鎖:A a=new A(),由于new出的對(duì)象是獨(dú)一無二的,所以只要有線程拿到了相應(yīng)的對(duì)象鎖,導(dǎo)致所有帶有synchronized(a)的方法及代碼塊都會(huì)被鎖住,只有線程釋放了這個(gè)鎖其他的方法或者是代碼塊才能被其他線程執(zhí)行,而synchronized(this)也是個(gè)對(duì)象鎖,this是當(dāng)前對(duì)象鎖,如果A a=new A(),a內(nèi)部有synchronized(this),那么synchronized(a)和synchronized(this)效果是一樣,都是一樣的對(duì)象鎖,所以會(huì)產(chǎn)生同步,有線程使用synchronized(a)進(jìn)入同步代碼塊后,那么其他線程將無法進(jìn)入a這個(gè)實(shí)例對(duì)象內(nèi)部帶有synchronized(this)的代碼塊,除非鎖釋放了
類鎖:synchronized(A.class),和對(duì)象鎖一樣由于類也是獨(dú)一無二的,所以只要有線程拿到了相應(yīng)的類鎖,導(dǎo)致所有帶有synchronized(A.class)的方法及代碼塊都會(huì)被鎖住,只有線程釋放了這個(gè)鎖其他的方法或者是代碼塊才能被其他線程執(zhí)行
方法鎖:非靜態(tài)的方法鎖public void synchronized aaa(),只針對(duì)當(dāng)前對(duì)象的某個(gè)方法,這個(gè)鎖就是當(dāng)前對(duì)象實(shí)例,只要沒有獲得這個(gè)當(dāng)前對(duì)象實(shí)例鎖的線程都不能訪問。靜態(tài)的方法鎖public static void synchronized aaa(),針對(duì)某個(gè)類,這個(gè)鎖就是當(dāng)前這個(gè)對(duì)象的類實(shí)例,只要沒有獲得這個(gè)當(dāng)前對(duì)象類的鎖的線程都不能訪問
synchronized通過對(duì)象頭和monitor實(shí)現(xiàn)的
對(duì)象頭:
synchronized的鎖對(duì)象是存儲(chǔ)在對(duì)象頭里,下面是對(duì)象頭的組成,主要由Mark Word 和Class Metadata Address組成,由于synchronized是重量級(jí)鎖性能差,jvm對(duì)進(jìn)行了優(yōu)化,利用對(duì)象頭實(shí)現(xiàn)了其而對(duì)象頭實(shí)現(xiàn)了輕量級(jí)鎖和偏向鎖

鎖優(yōu)化:
由于synchronized鎖過于重量級(jí),在性能上一直都很差,比ReentrantLock差,HotSpot一直對(duì)鎖進(jìn)行優(yōu)化如:自旋鎖、自適應(yīng)自旋鎖、鎖粗化、鎖消除、輕量級(jí)鎖、偏向鎖等,現(xiàn)在synchronized性能不比ReentrantLock差,并且還在不斷的優(yōu)化中,由于都是系統(tǒng)級(jí)的優(yōu)化,所以synchronized前途是比ReentrantLock光明的
重量級(jí)鎖:
最基礎(chǔ)的實(shí)現(xiàn)方式,JVM會(huì)阻塞未獲取到鎖的線程,在鎖被釋放的時(shí)候喚醒這些線程。阻塞和喚醒操作是依賴操作系統(tǒng)來完成的,所以需要從用戶態(tài)切換到內(nèi)核態(tài),開銷很大。加上monitor調(diào)用的是操作系統(tǒng)底層的互斥量(mutex),而使用操作系統(tǒng)本身也有用戶態(tài)和內(nèi)核態(tài)的切換,又增加了開銷,所以JVM引入了自旋的概念,減少上面說的線程切換的成本。
自旋鎖:
如果鎖被其他線程占用的時(shí)間很短,那么其他獲取鎖的線程只要稍微等一下就好了,主要是使線程不放棄CPU時(shí)間然后執(zhí)行忙循環(huán),沒必要進(jìn)行用戶態(tài)和內(nèi)核態(tài)之間的切換,等的狀態(tài)就叫自旋。在JDK6中是默認(rèn)開啟的通過-XX:-/+UseSpinning設(shè)置關(guān)閉/開啟,通過-XX:PreBlockSpin設(shè)置自旋次數(shù),默認(rèn)是10。在JDK6中引入了自適應(yīng)自旋鎖,使自旋的時(shí)間不在固定,并且可以省略自旋,由同一個(gè)鎖對(duì)象的上次自旋時(shí)間決定是否需要省略本次的自旋操作,如果一個(gè)鎖對(duì)象自旋很少成功,虛擬機(jī)會(huì)直接放棄自旋,相反如果同一鎖對(duì)象自旋經(jīng)常成功虛擬機(jī)會(huì)允許它自旋得久一些。這種機(jī)制讓虛擬機(jī)一定程度上節(jié)省了自旋浪費(fèi)的時(shí)間。
鎖消除:
虛擬機(jī)會(huì)檢測(cè)我們的代碼中不可能出現(xiàn)出現(xiàn)數(shù)據(jù)共享競爭的地方,將多余的鎖進(jìn)行消除
鎖粗化:
如果一系列操作都對(duì)同一個(gè)對(duì)象反復(fù)的加鎖和解鎖,這種現(xiàn)象甚至出現(xiàn)在循環(huán)體中,對(duì)系統(tǒng)的造成不必要的消耗,那么虛擬機(jī)將會(huì)把這個(gè)鎖的范圍擴(kuò)大,把多次加鎖解鎖的地方變成只需要一次加鎖和解鎖
輕量級(jí)鎖:
JDK1.6之后加入,它的目的并不是為了替換前面的重量級(jí)鎖,而是在實(shí)際沒有鎖競爭的情況下,將申請(qǐng)互斥量這步也省掉。鎖實(shí)現(xiàn)的核心在于對(duì)象頭(Mark Word)的結(jié)構(gòu),對(duì)象自身會(huì)有信息表示是否被鎖住和鎖是什么類型的,如果代碼進(jìn)入同步塊時(shí),檢測(cè)到對(duì)象未鎖定,即標(biāo)志位為01。那么當(dāng)前線程就會(huì)在自身?xiàng)薪⒁粋€(gè)名為鎖記錄的區(qū)域拷貝一份對(duì)象的Mark Word信息叫做Displace Mark Word,再使用CAS的方式將對(duì)象Mark Work更改為指向這個(gè)區(qū)域的指針,如果更改成功了就算加上鎖了成功了,那么當(dāng)前線程就算擁有了這個(gè)對(duì)象的鎖了,上鎖成功后這個(gè)對(duì)象的Mark Word的鎖標(biāo)志位將會(huì)變?yōu)?0。(這樣就不需要獲取系統(tǒng)mutex變量,只是改了個(gè)值,但是如果有競爭的話,就要升級(jí)成重量級(jí)鎖,這樣反倒變慢了)
偏向鎖:
偏向鎖將同步操作全部省略,進(jìn)一步的對(duì)鎖進(jìn)行了優(yōu)化,-XX:+UseBiasedLocking可以開啟,它主要實(shí)現(xiàn)是一個(gè)對(duì)象鎖第一次被線程獲取的時(shí)候?qū)ο箢^的標(biāo)志位會(huì)被設(shè)置為“01”表示偏向模式,同時(shí)使用CAS把獲取這個(gè)對(duì)象鎖的線程ID記錄在對(duì)象的Mark Word中,如果CAS成功代表獲取偏向鎖成功,持有這個(gè)偏向鎖的線程每次進(jìn)入這個(gè)對(duì)象鎖相關(guān)的代碼塊時(shí),虛擬機(jī)將不會(huì)對(duì)這個(gè)線程做任何操作,但是如果有其他的線程來嘗試獲取這個(gè)對(duì)象鎖的時(shí)候,偏向模式就會(huì)被撤銷,標(biāo)志位將會(huì)變?yōu)闊o鎖的"01"或者輕量級(jí)鎖的"00",由此可以看出鎖是一個(gè)逐步升級(jí)的過程,不會(huì)一開始上來就重量級(jí)鎖。鎖一般只會(huì)升級(jí)不會(huì)降級(jí),避免降級(jí)之后沖突導(dǎo)致效率不行并且又得升級(jí)。但是降級(jí)其實(shí)是允許的
偏向鎖可以提高帶有同步但無競爭的程序的性能,但是鎖總是會(huì)被多個(gè)線程競爭的,所以偏向模式將會(huì)是多余的,還是禁止掉偏向鎖優(yōu)化比較好。-XX:-UseBiasedLocking禁止
隱式鎖 monitor:
每個(gè)對(duì)象里面隱式的存在一個(gè)叫monitor(對(duì)象監(jiān)視器)的對(duì)象,這個(gè)對(duì)象源碼是采用C++實(shí)現(xiàn)的,monitor內(nèi)部有一個(gè)count變量,調(diào)用monitorenter就是嘗試獲取這個(gè)對(duì)象,成功獲取到了就將值+1,離開就將值-1。如果是線程重入,在將值+1,說明monitor對(duì)象是支持可重入的。如果synchronize在方法上,那就沒有上面兩個(gè)指令,取而代之的是有一個(gè)ACC_SYNCHRONIZED修飾,表示方法加鎖了。它會(huì)在常量池中增加這個(gè)一個(gè)標(biāo)識(shí)符,獲取它的monitor,所以本質(zhì)上是對(duì)象鎖是一樣的。
1、monitorenter: monitorenter指令表示獲取鎖對(duì)象的monitor對(duì)象,這是monitor對(duì)象中的count并會(huì)加+1,如果monitor已經(jīng)被其他線程所獲取,該線程會(huì)被阻塞住,直到count=0,再重新嘗試獲取monitor對(duì)象
2、monitorexit: monitorexit與monitorenter是相對(duì)的指令,表示進(jìn)入和退出。執(zhí)行monitorexit指令表示該線程釋放鎖對(duì)象的monitor對(duì)象,這時(shí)monitor對(duì)象的count便會(huì)-1變成0,其他被阻塞的線程可以重新嘗試獲取鎖對(duì)象的monitor對(duì)象,第二monitorexit是主要作用是發(fā)生異常的時(shí)候可以自動(dòng)釋放鎖
3、synchronized用來修飾方法時(shí)(靜態(tài)方法和非靜態(tài)方法),是通過ACC_SYNCHRONIZED標(biāo)識(shí)符來保持線程同步的。而用來修飾代碼塊時(shí),是通過monitorenter和monitorexit指令來完成,ACC_SYNCHRONIZED修飾時(shí)它同樣是取獲取monitor對(duì)象的鎖,所以本質(zhì)上和對(duì)象鎖一樣
每一個(gè)鎖都對(duì)應(yīng)一個(gè)monitor對(duì)象,在HotSpot虛擬機(jī)中它是由ObjectMonitor實(shí)現(xiàn)的(C++實(shí)現(xiàn))。每個(gè)對(duì)象都存在著一個(gè)monitor與之關(guān)聯(lián),對(duì)象與其monitor之間的關(guān)系有存在多種實(shí)現(xiàn)方式,如monitor可以與對(duì)象一起創(chuàng)建銷毀或當(dāng)線程試圖獲取對(duì)象鎖時(shí)自動(dòng)生成,但當(dāng)一個(gè)monitor被某個(gè)線程持有后,它便處于鎖定狀態(tài)。下圖是ObjectMonitor的C++源碼

ObjectMonitor中有兩個(gè)隊(duì)列_WaitSet和_EntryList,用來保存ObjectWaiter對(duì)象列表(每個(gè)等待鎖的線程都會(huì)被封裝ObjectWaiter對(duì)象),_owner指向持有ObjectMonitor對(duì)象的線程,當(dāng)多個(gè)線程同時(shí)訪問一段同步代碼時(shí),首先會(huì)進(jìn)入_EntryList 集合,當(dāng)線程獲取到對(duì)象的monitor 后進(jìn)入 _Owner 區(qū)域并把monitor中的owner變量設(shè)置為當(dāng)前線程同時(shí)monitor中的計(jì)數(shù)器count加1,若線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的monitor,owner變量恢復(fù)為null,count自減1,同時(shí)該線程進(jìn)入 WaitSe t集合中等待被喚醒。若當(dāng)前線程執(zhí)行完畢也將釋放monitor(鎖)并復(fù)位變量的值,以便其他線程進(jìn)入獲取monitor(鎖)。
monitor對(duì)象存在于每個(gè)Java對(duì)象的對(duì)象頭中(存儲(chǔ)的指針的指向),synchronized鎖便是通過這種方式獲取鎖的,也是為什么Java中任意對(duì)象可以作為鎖的原因,同時(shí)也是notify/notifyAll/wait等方法存在于頂級(jí)對(duì)象Object中的原因(關(guān)于這點(diǎn)稍后還會(huì)進(jìn)行分析)
一個(gè)monitor對(duì)象包括這么幾個(gè)關(guān)鍵字段:_cxq(下圖中的ContentionList),_EntryList ,_WaitSet,_owner。其中_cxq ,_EntryList ,_WaitSet都是由ObjectWaiter組成的鏈表結(jié)構(gòu),_owner指向持有鎖的線程。當(dāng)一個(gè)線程嘗試獲得鎖時(shí),如果該鎖已經(jīng)被占用,則會(huì)將該線程封裝成一個(gè)ObjectWaiter對(duì)象插入到_cxq的隊(duì)列尾部,然后暫停當(dāng)前線程。當(dāng)持有鎖的線程釋放鎖前,會(huì)將cxq中的所有元素移動(dòng)到_EntryList中去,并喚醒_EntryList的隊(duì)首線程。如果一個(gè)線程在同步塊中調(diào)用了wait方法,會(huì)將該線程從_EntryList移除并加入到_WaitSet中,然后釋放鎖。當(dāng)wait的線程被notify之后,會(huì)將對(duì)應(yīng)的線程從_WaitSet移動(dòng)到_EntryList中

利用javap -verbose 反編譯這段代碼
public class SynchronizedTest {
public synchronized void methodtest(){
System.out.println("方法鎖");
}
public void objectlock(){
synchronized (this){
System.out.println("對(duì)象鎖");
}
}
public void classlock(){
synchronized (SiSuo.class) {
System.out.println("類鎖");
}
}
public synchronized static void staticlock(){
System.out.println("靜態(tài)方法鎖");
}
public static void main(String[] args) {
SynchronizedTest test=new SynchronizedTest();
test.methodtest();
test.objectlock();
test.classlock();
SynchronizedTest.staticlock();
}
}




線程的狀態(tài):
由上面分析的monitor知道線程在獲取鎖時(shí)會(huì)被封裝成monitor進(jìn)入ContentionList,EntryList ,WaitSet,owner幾個(gè)隊(duì)列代表著線程的不同狀態(tài)
線程中共有六種狀態(tài):
1、新建(New):被創(chuàng)建(new)出來,但是沒有啟動(dòng)的線程
2、運(yùn)行(Runnable):運(yùn)行狀態(tài)又分為兩種狀態(tài):就緒和運(yùn)行。線程被創(chuàng)建,并行執(zhí)行start后,線程進(jìn)入可運(yùn)行線程池中,等待CPU執(zhí)行時(shí)間,此時(shí)是就緒狀態(tài)。線程就緒狀態(tài)獲取CPU執(zhí)行時(shí)間后進(jìn)入運(yùn)行狀態(tài)
3、無限期等待(Waiting):此狀態(tài)線程不會(huì)獲得CPU執(zhí)行時(shí)間,除非被主動(dòng)喚醒
4、限期等待(Time Waiting):此狀態(tài)的線程不會(huì)獲得CPU執(zhí)行時(shí)間,直至被系統(tǒng)喚醒。設(shè)置了Thread.sleep();
5、阻塞(Blocked):線程等待獲取排它鎖
6、結(jié)束(Terminated):線程已經(jīng)終止,線程執(zhí)行結(jié)束。在一個(gè)終止的線程執(zhí)行操作如執(zhí)行:t.start(),會(huì)拋出java.lang.IllegalThreadStateException
線程的這些狀態(tài)可以通過sleep、wait、notify、notifyAll、yield、interrupt進(jìn)行轉(zhuǎn)換
sleep不必多說,平時(shí)我們用過,它可以使線程進(jìn)入期限等待中,sleep狀態(tài)下的線程只會(huì)讓出CPU執(zhí)行時(shí)間,不會(huì)讓出鎖
interrupt:提醒系統(tǒng)線程應(yīng)該被終止,如果線程處于阻塞狀態(tài),線程會(huì)退出阻塞如Thread.sleep();,拋出異常。如果線程是正?;顒?dòng)的,將會(huì)把線程的中斷標(biāo)志設(shè)置為true,然后線程正常運(yùn)行不受影響.interrupt中斷僅僅是設(shè)置中斷標(biāo)記位。對(duì)于NEW|TERMINATED 線程,終端完全不起作用;對(duì)于RUNNABLE或者BLOCKED線程,只會(huì)將中斷標(biāo)志位設(shè)為true;WAITING線程對(duì)中斷較為敏感,會(huì)拋出異常,同時(shí)會(huì)將中斷標(biāo)志位清除變?yōu)閒alse。
public void interrupt(): 設(shè)置當(dāng)前線程的中斷標(biāo)志位,非WAITING線程不會(huì)受任何影響的,僅此而已。
public boolean isInterrupted(): 判斷當(dāng)前線程中斷標(biāo)志位是否被標(biāo)記為中斷,不會(huì)清除標(biāo)志位
public static boolean interrupted():這是一個(gè)靜態(tài)方法,返回當(dāng)前線程是否標(biāo)記中斷,同時(shí)清除標(biāo)志位
wait、notify、notifyAll:
wait:線程執(zhí)行wait,讓線程進(jìn)入無限期等待狀態(tài),只有其他線程執(zhí)行notify或者notifyall才被喚醒,wait只能在synchronized的代碼塊或在synchronized類中使用,它會(huì)讓線程不但需要讓出CPU執(zhí)行時(shí)間,還必須讓出鎖給其他線程
wait被執(zhí)行后,線程失去CPU時(shí)間和鎖,線程被扔進(jìn)等待池,此線程在等待池中不會(huì)去競爭鎖,直至有其他線程對(duì)鎖執(zhí)行notify或者notifyall,線程才會(huì)被扔進(jìn)鎖池中去競爭鎖,notify和notifyall的區(qū)別在于,notify只會(huì)隨機(jī)喚醒一個(gè)wait()的線程,而notifyall會(huì)喚醒所有wait()的線程
yield:線程暗示CPU,愿意讓出CPU時(shí)間給其他線程,但是CPU不一定會(huì)理會(huì)這個(gè)暗示,最終的決定線程是否讓出CPU時(shí)間由CPU決定
此外還有一個(gè)join方法,代表著“插隊(duì)”,哪個(gè)線程調(diào)用join代表哪個(gè)線程插隊(duì)先執(zhí)行——但是插誰的隊(duì)是有講究了,不是說你可以插到隊(duì)頭去做第一個(gè)吃螃蟹的人,而是插到在當(dāng)前運(yùn)行線程的前面,比如系統(tǒng)目前運(yùn)行線程A,在線程A里面調(diào)用了線程B.join方法,則接下來線程B會(huì)搶先在線程A面前執(zhí)行,等到線程B全部執(zhí)行完后才繼續(xù)執(zhí)行線程A。join內(nèi)部是調(diào)用了wait方法對(duì)目前正在運(yùn)行的線程進(jìn)行阻塞,注意這里線程執(zhí)行后是調(diào)用notifyAll進(jìn)行喚醒的,也就是B調(diào)用notifyAll喚醒A
安全發(fā)布對(duì)象:
- 1、在靜態(tài)初始化函數(shù)中初始化一個(gè)對(duì)象引用
- 2、將對(duì)象的引用保存到volatile類型域或者AtomicReference對(duì)象中
- 3、將對(duì)象的引用保存到某個(gè)正確構(gòu)造的final類型域中
- 4、將對(duì)象的引用保存到一個(gè)由鎖保護(hù)的域中
單例模式安全發(fā)布演示
//線程安全
//懶漢
public class Singleton {
private Singleton(){}
//使用volatile禁止指令重排
private static volatile Singleton unsafeObject=null;
public static Singleton unsafeObject(){
if (unsafeObject==null){
synchronized (Singleton.class){
if (unsafeObject==null) {
return new Singleton();
}
}
}
return unsafeObject;
}
}
//線程安全
//餓漢
public class Singleton {
private Singleton(){}
//使用volatile禁止指令重排
private static Singleton unsafeObject=new Singleton();
public static Singleton unsafeObject(){
return unsafeObject;
}
}
//枚舉方式線程安全
public class SingletonEunm {
private SingletonEunm(){}
//枚舉類相比餓漢模式更加節(jié)省資源,原因是枚舉調(diào)用的時(shí)候才創(chuàng)建,并且只創(chuàng)建一次,不像靜態(tài)代碼在項(xiàng)目啟動(dòng)時(shí)提前創(chuàng)建好,資源浪費(fèi)
private static SingletonEunm getSingletonEunm(){
return InstanceSingleton.INSTANCE.getSingleton();
}
private enum InstanceSingleton{
INSTANCE;
private SingletonEunm singletonEunm;
//對(duì)于枚舉類JVM保證只初始化一次,并且是調(diào)用的時(shí)候才初始化
InstanceSingleton(){
singletonEunm=new SingletonEunm();
}
public SingletonEunm getSingleton(){
return singletonEunm;
}
}
}
使用final安全發(fā)布演示
public class Final {
private static final int SAFEINT=2333;//這個(gè)基本類型不可修改,安全
//SAFESTR這個(gè)引用不可在指向其他對(duì)象,“2333”字面量也是對(duì)象,所以SAFESTR這個(gè)引用無法指向其他對(duì)象也就是無法修改它的字面量了,安全
private static final String SAFESTR="2333";
//ALLOW這個(gè)引用無法指向其他的對(duì)象了,但是可以修改指向?qū)ο蟮膬?nèi)容
private static final Map<String,String> ALLOWMAP= new HashMap();
private static Map<String,String> UNMAP= new HashMap();
//利用Gava使GUNMAP內(nèi)容不可變,final 使GUNMAP引用不可指向其他對(duì)象
private static final Map GUNMAP= ImmutableMap.of("k1","v1","k2","v2");
private static final List GUNLIST= ImmutableList.of("111","222");
private static final Set GUNSET= ImmutableSet.of("111","222");
static {
UNMAP.put("111","222");
//使用Collections.unmodifiableMap使Map里的內(nèi)容不可變
UNMAP= Collections.unmodifiableMap(UNMAP);
}