1.JMM數(shù)據(jù)原子操作
- read(讀取)∶從主內(nèi)存讀取數(shù)據(jù)
- load(載入):將主內(nèi)存讀取到的數(shù)據(jù)寫(xiě)入工作內(nèi)存
- use(使用):從工作內(nèi)存讀取數(shù)據(jù)來(lái)計(jì)算
- assign(賦值):將計(jì)算好的值重新賦值到工作內(nèi)存中
- store(存儲(chǔ)):將工作內(nèi)存數(shù)據(jù)寫(xiě)入主內(nèi)存
- write(寫(xiě)入):將store過(guò)去的變量值賦值給主內(nèi)存中的變量
- lock(鎖定):將主內(nèi)存變量加鎖,標(biāo)識(shí)為線程獨(dú)占狀態(tài)
- unlock(解鎖):將主內(nèi)存變量解鎖,解鎖后其他線程可以鎖定該變量
2.來(lái)看volatile關(guān)鍵字
(1)啟動(dòng)兩個(gè)線程
public class VolatileDemo {
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!flag){
}
System.out.println("跳出while循環(huán)了");
}).start();
Thread.sleep(2000);
new Thread(() -> changeFlage()).start();
}
private static void changeFlage() {
System.out.println("開(kāi)始改變flag值之前");
flag = true;
System.out.println("改變flag值之后");
}
}
沒(méi)加volatile之前,第一個(gè)線程的while判斷一直滿足
(2)給變量flag加了volatile之后
public class VolatileDemo {
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!flag){
}
System.out.println("跳出while循環(huán)了");
}).start();
Thread.sleep(2000);
new Thread(() -> changeFlage()).start();
}
private static void changeFlage() {
System.out.println("開(kāi)始改變flag值之前");
flag = true;
System.out.println("改變flag值之后");
}
}
while語(yǔ)句能夠滿足條件
(3)原理解釋?zhuān)?/p>
開(kāi)啟第一個(gè)線程時(shí),flag變量通過(guò)read從主內(nèi)存中讀出數(shù)據(jù),使用load把數(shù)據(jù)加載進(jìn)線程一的工作內(nèi)存,通過(guò)use把flag讀取到線程中;線程二也是同樣的讀取操作。線程二通過(guò)assign改變了flag的值,線程二工作內(nèi)存中存儲(chǔ)的flag=true,再通過(guò)store把flag寫(xiě)入到總線,總線再把flag通過(guò)write寫(xiě)入到住內(nèi)存;由于兩個(gè)線程讀取操作的都是各種工作內(nèi)存中的值,是主內(nèi)存的副本,相互不通信,所以線程一一直再循環(huán),線程一的flag為false。
加了volatile后,添加了緩存一致性協(xié)議(MESI),CPU通過(guò)總線嗅探機(jī)制感知到數(shù)據(jù)的變化而自己緩存里的值失效,此時(shí)線程一會(huì)把工作內(nèi)存中存放的flag失效,從主內(nèi)存中重新讀取flag的值,此時(shí)滿足while條件。
volatile底層通過(guò)匯編語(yǔ)言的lock修飾,當(dāng)變量有修改立馬寫(xiě)回主類(lèi),避免指令重排序
3.并發(fā)編程三大特性
可見(jiàn)性,有序性、原子性
4.雙鎖判斷機(jī)制創(chuàng)建單例模式
public class DoubleCheckLockSinglenon {
private static volatile DoubleCheckLockSinglenon doubleCheckLockSingleon = null;
public DoubleCheckLockSinglenon(){}
public static DoubleCheckLockSinglenon getInstance(){
if (null == doubleCheckLockSingleon) {
synchronized(DoubleCheckLockSinglenon.class){
if(null == doubleCheckLockSingleon){
doubleCheckLockSingleon = new DoubleCheckLockSinglenon();
}
}
}
return doubleCheckLockSingleon;
}
public static void main(String[] args) {
System.out.println(DoubleCheckLockSinglenon.getInstance());
}
}
當(dāng)線程調(diào)用getInstance方法創(chuàng)建的時(shí)候,先判斷是否為空,為空則把對(duì)象加上鎖,否則多線程的情況會(huì)創(chuàng)建重復(fù),再鎖里面再次判斷是否為空,當(dāng)new一個(gè)對(duì)象的時(shí)候,先在內(nèi)存分配空間,再執(zhí)行對(duì)象的init屬性賦零操作,再執(zhí)行初始化賦值操作。
cpu為了優(yōu)化代碼執(zhí)行效率,會(huì)對(duì)滿足as-if-serial和happens-before原則的代碼進(jìn)行指令重排序,as-if-serial規(guī)定線程內(nèi)的執(zhí)行代碼順序不影響結(jié)果輸出,則會(huì)進(jìn)行指令重排;
happens-before規(guī)定一些鎖的順序,同一個(gè)對(duì)象的unlock需要出現(xiàn)下一個(gè)lock之前等。
所以為了防止new的時(shí)候,指令重排,先進(jìn)行賦值再執(zhí)行賦零操作情況,需要加上volatile修飾符,加上volatile修飾后,在new操作時(shí)會(huì)創(chuàng)建內(nèi)存屏障,高速cpu不進(jìn)行指令重排序,底層是lock關(guān)鍵字;內(nèi)存屏障分為L(zhǎng)oadLoad(讀讀)、storestore(寫(xiě)寫(xiě))、loadstore(讀寫(xiě))、storeload(寫(xiě)讀),底層是c++代碼寫(xiě)的,c++代碼再調(diào)用匯編語(yǔ)言
5.synchronized關(guān)鍵字
(1)沒(méi)加synchronized之前
package com.qingyun;
/**
* Synchronized關(guān)鍵字
*/
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Num num = new Num();
Thread t1 = new Thread(() -> {
for (int i = 0;i < 100000;i++) {
num.incrent();
}
});
t1.start();
for (int i = 0;i < 100000;i++) {
num.incrent();
}
t1.join();
System.out.println(num.getNum());
}
}
package com.qingyun;
public class Num {
public int num = 0;
public void incrent() {
num++;
}
public int getNum(){
return num;
}
}
輸出結(jié)果不是我們想要的,由于線程和for循環(huán)同時(shí)去調(diào)加的方法,導(dǎo)致最后輸出的結(jié)果不是我們想要的
(2)加上synchronized之后
public synchronized void incrent() {
num++;
}
//或者
public void incrent() {
synchronized(this){
num++;
}
}
輸出的結(jié)果是我們想要的,synchronized關(guān)鍵字底層使用的lock,是重量級(jí)鎖,互斥鎖、悲觀鎖,jdk1.6之前的鎖,線程會(huì)放到一個(gè)隊(duì)列里面等待著執(zhí)行
6.AtomicIntger原子操作
(1)給原子加1的操作,可以使用AtomicInteger實(shí)現(xiàn),與synchronized相比,性能大大提升
public class Num {
// public int num = 0;
AtomicInteger atomicInteger = new AtomicInteger();
public void incrent() {
atomicInteger.incrementAndGet(); //原子加1
}
public int getNum(){
return atomicInteger.get();
}
}
AtomicInteger源碼有一個(gè)value字段,使用volatile修飾,volatile底層使用lock修飾,保證多線程并發(fā)結(jié)果的正確
private volatile int value;
(2)atomicInteger.incrementAndGet()方法做的事情:先獲取到value的值,給值加1,再使用舊的值和atomicInteger進(jìn)行比較,相等了把newValue設(shè)置進(jìn)去,由于使用多線程可能值會(huì)不相等的情況,所以使用while進(jìn)行循環(huán)比對(duì),相等了執(zhí)行完才推出
while(true) {
int oldValue = atomicInteger.get();
int newValue = oldValue+1;
if(atomicInteger.compareAndSet(oldValue,newValue)){
break;
}
}
(3)atomicInteger.compareAndSet比對(duì)完值后才設(shè)置新值的方式即為CAS:無(wú)鎖、樂(lè)觀鎖、輕量級(jí)鎖,synchroznied存在線程阻塞、上行文切換、操作系統(tǒng)調(diào)度比較費(fèi)時(shí);CAS一直循環(huán)比對(duì)執(zhí)行,效率要高
(4)compareAndSetInt底層使用native修飾,底層是c++代碼,實(shí)現(xiàn)了原子性問(wèn)題,在匯編語(yǔ)言使用代碼lock cmpxchqq保證了原子性,是緩存行鎖
(5)ABA問(wèn)題:線程一那到一個(gè)變量,線程二執(zhí)行比較快,也拿到這個(gè)變量,把變量的值進(jìn)行修改,再快速修改回原來(lái)的值,這樣變量的值有過(guò)一次變化,線程一再去執(zhí)行compareAndSet的時(shí)候,雖然值還是之前的沒(méi)變,但是已經(jīng)發(fā)生過(guò)變化了,出現(xiàn)ABA問(wèn)題
(6)解決ABA問(wèn)題就是給變量加版本,每次操作變量版本加1,JDK帶版本的鎖有AtomicStampedReference,這樣就算變量被其它線程修改過(guò)再回復(fù)原值,版本號(hào)也是不一致的。
7.鎖優(yōu)化
(1)重量級(jí)鎖會(huì)把等待的線程放到隊(duì)列中,重量級(jí)鎖鎖定的是monitor,存在上下問(wèn)切換的資源占用;輕量級(jí)鎖若是線程太多,會(huì)存在自旋,耗費(fèi)cpu
(2)jdk1.6之后,鎖升級(jí)為無(wú)狀態(tài)-》偏向鎖(鎖id指定)-》輕量級(jí)鎖(自旋膨脹)-》重量級(jí)鎖(隊(duì)列存儲(chǔ))
(3)創(chuàng)建一個(gè)對(duì)象,此時(shí)對(duì)象為無(wú)狀態(tài),當(dāng)啟動(dòng)了一個(gè)線程時(shí),再創(chuàng)建一個(gè)對(duì)象時(shí),啟用偏向鎖,偏向鎖執(zhí)行完之后不會(huì)釋放鎖;當(dāng)再啟用一個(gè)線程時(shí),有兩個(gè)線程來(lái)掙搶對(duì)象時(shí),立馬又偏向鎖升級(jí)為輕量級(jí)鎖;當(dāng)再創(chuàng)建一個(gè)線程的來(lái)掙搶對(duì)象鎖時(shí),由輕量級(jí)鎖升級(jí)為重量級(jí)鎖
(4)分段CAS,底層有一個(gè)base記錄變量值,當(dāng)有多個(gè)線程類(lèi)訪問(wèn)此變量是,base的值會(huì)分為多個(gè)cell,組成數(shù)組,每個(gè)cell對(duì)應(yīng)一到多個(gè)線程的cas處理,避免了線程的自旋空轉(zhuǎn),這樣還是輕量級(jí)鎖,返回?cái)?shù)據(jù)的時(shí)候,底層調(diào)用的是所有cell數(shù)組和base的加和
public class Num {
LongAdder longAdder = new LongAdder();
public void incrent() {
longAdder.increment();
}
public long getNum(){
return longAdder.longValue();
}
}
public long longValue() {
return sum();
}
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
到此本篇關(guān)于Java并發(fā)編程中的多線程高并發(fā)一些重要知識(shí)的內(nèi)容詳解文章就介紹到這結(jié)束了,更多相關(guān)Java并發(fā)編程 多線程 高并發(fā)的內(nèi)容,請(qǐng)搜索W3Cschool以前的文章或繼續(xù)瀏覽下面的相關(guān)文章!