Java多線程volatile原理及用法解析
首先volatile有兩大功能:
保證線程可見(jiàn)性
禁止指令重排序
1、保證線程可見(jiàn)性
首先我們來(lái)看這樣一個(gè)程序,其中不加volatile關(guān)鍵字運(yùn)行的結(jié)果截然不同,加上volatile程序能夠正常結(jié)束,不加則程序進(jìn)入死循環(huán);
package com.designmodal.design.juc01;
import java.util.concurrent.TimeUnit;
/**
* @author D-L
* @Classname T001_volatile
* @Version 1.0
* @Description volatile 保證線程的可見(jiàn)性
* @Date 2020/7/19 17:30
*/
public class T001_volatile {
//定義一個(gè)變量running
volatile boolean running = true;
public void m(){
while(running){
//TODO 不做任何的處理
System.out.println("while is running When can I stop -------------");
}
System.out.println("method is end ---------------");
}
public static void main(String[] args) {
T001_volatile t001_volatile = new T001_volatile();
new Thread(t001_volatile::m , "Thread t1").start();
//停一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改running的值
t001_volatile.running = false;
}
}
通過(guò)上面的小程序說(shuō)明volatile是具有保證線程之間的可見(jiàn)性的功能的,具體是如何實(shí)現(xiàn)的呢?下面給大家解釋一下:
之前在上一篇講synchronized時(shí)提到了 堆內(nèi)存是線程共享的,而線程在工作時(shí)有自己的工作內(nèi)存,對(duì)于共享變量running來(lái)說(shuō),線程1和線程2在運(yùn)行的時(shí)候先把running變量copy到自己工作內(nèi)存,對(duì)這個(gè)變量的改變都是在自己的工作內(nèi)存中,并不會(huì)直接的反映到其他線程,如果加了volatile,running變量改變其他線程很快就會(huì)知道,這就是線程的可見(jiàn)性;

這里用到的是:MESI(CPU緩存一致性協(xié)議) MESI的主要思想:當(dāng)CPU寫數(shù)據(jù)時(shí),如果該變量是共享數(shù)據(jù),給其他CPU發(fā)送信號(hào),使得其他的CPU中的該變量的緩存行無(wú)效;歸根結(jié)底這里需要借助硬件來(lái)幫助我們。

volatile保證線程可見(jiàn)性但是不能代替synchronized:
package com.designmodal.design.juc01;
import java.util.ArrayList;
import java.util.List;
/**
* @author D-L
* @Classname VolatileAndSynchronized
* @Version 1.0
* @Description synchronized can not be replaced by volatile
* volatile 不能代替synchronized
* 只能保證可見(jiàn)性 不能保證原子性
* count++ 不是原子性操作
* @Date 2020/xx/xx 23:25
*/
public class VolatileAndSynchronized {
volatile int count = 0;
public synchronized void m(){
for (int i = 0; i < 1000; i++) {
//非原子性操作 匯編指令至少有三條
count++;
}
}
public static void main(String[] args) {
VolatileAndSynchronized v = new VolatileAndSynchronized();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(v::m , "Thread"+ i));
}
threads.forEach(o ->o.start());
threads.forEach(o ->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(v.count);
}
}
2、禁止指令重排序
指令重排序也是和CPU有關(guān)系,加了volatile之后,每次寫都會(huì)背線程看到。CPU原來(lái)執(zhí)行指令時(shí),是按照一步一步順序來(lái)執(zhí)行的,但是CPU為了提高效率它會(huì)把指令并發(fā)來(lái)執(zhí)行,第一個(gè)指令執(zhí)行到一半的時(shí)候第二條指令就可能已經(jīng)開(kāi)始執(zhí)行了,這叫流水線式的執(zhí)行;為了充分的利用CPU,就要求編譯器把編譯完的源碼指令,可能會(huì)進(jìn)行一個(gè)指令重新排序;這種架構(gòu)通過(guò)實(shí)際驗(yàn)證,很大效率上提高了CPU的使用效率。
下面從一個(gè)面試題來(lái)討論一下指令重排序:
面試官:你聽(tīng)過(guò)單例模式嗎?
你:當(dāng)然聽(tīng)過(guò),不然沒(méi)法聊了。
package com.designmodal.design.juc01;
import java.util.concurrent.TimeUnit;
/**
* @author D-L
* @Classname T002_volatile
* @Version 1.0
* @Description volatile 指令重排序
* @Date 2020/7/20 00:48
*/
public class T002_volatile {
//創(chuàng)建私有的 T002_volatile 有人會(huì)問(wèn)這里的volatile要不要使用,這里的答案是肯定的
private static /**volatile*/ volatile T002_volatile INSTANCE;
public T002_volatile() {}
public T002_volatile getInstance(){
//模擬業(yè)務(wù)代碼 這里為了synchronized更加細(xì)粒度,所以使用了雙重檢查
if(INSTANCE == null){
synchronized (this){
//雙重檢查
if(INSTANCE == null){
//避免線程之間的干擾 在這里睡一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//創(chuàng)建實(shí)例對(duì)象
INSTANCE = new T002_volatile();
}
}
}
return INSTANCE;
}
/**
* 創(chuàng)建100個(gè)線程 調(diào)用getInstance() 打印hashcode值
* @param args
*/
public static void main(String[] args) {
T002_volatile t001_volatile = new T002_volatile();
for (int i = 0; i < 100; i++) {
new Thread(() ->{
T002_volatile instance = t001_volatile.getInstance();
System.out.println(instance.hashCode());
}).start();
}
}
}
在上述的代碼中:INSTANCE = new T002_volatile(); 經(jīng)過(guò)編譯后的指令是分三步的
1、給指令申請(qǐng)內(nèi)存
2、給成員變量初始化
3、把這塊對(duì)象的內(nèi)容賦給INSTANCE
在第二步這里既然已經(jīng)有默認(rèn)值了,第二個(gè)線程來(lái)檢查,發(fā)現(xiàn)已經(jīng)有值了根本就不會(huì)進(jìn)入鎖住的那份代碼;加了volatile就不會(huì)出現(xiàn)指令重排序了,所以在這個(gè)時(shí)候一定要保證初始化完成之后才會(huì)賦值給這個(gè)變量,這就是volatile存在的意義。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- JAVA并發(fā)中VOLATILE關(guān)鍵字的神奇之處詳解
- Java使用DualPivotQuicksort排序
- java中volatile關(guān)鍵字的作用與實(shí)例代碼
- Java中多線程與并發(fā)_volatile關(guān)鍵字的深入理解
- 談?wù)剬?duì)Java中的volatile的理解
- Java volatile如何實(shí)現(xiàn)禁止指令重排
- Java Volatile應(yīng)用單例模式實(shí)現(xiàn)過(guò)程解析
- Java并發(fā)編程——volatile關(guān)鍵字
- java實(shí)現(xiàn)相同屬性名稱及相似類型的pojo、dto、vo等互轉(zhuǎn)操作
- Java Method類及invoke方法原理解析
- Java編寫的實(shí)體返回VO工具
相關(guān)文章
Dubbo在Spring和Spring Boot中的使用詳解
這篇文章主要介紹了Dubbo在Spring和Spring Boot中的使用詳解,需要的朋友可以參考下2017-10-10
Java使用HttpUtils實(shí)現(xiàn)發(fā)送HTTP請(qǐng)求
這篇文章主要介紹了Java使用HttpUtils實(shí)現(xiàn)發(fā)送HTTP請(qǐng)求,HTTP請(qǐng)求,在日常開(kāi)發(fā)中,還是比較常見(jiàn)的,今天給大家分享HttpUtils如何使用,需要的朋友可以參考下2023-05-05
SpringBoot返回文件使前端下載的幾種方式小結(jié)
本文主要介紹了Spring Boot中幾種文件下載的方法,通過(guò)后端應(yīng)用下載文件并進(jìn)行業(yè)務(wù)處理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
Spring中PathMatcher路徑匹配器的實(shí)現(xiàn)
Spring框架中的PathMatcher是一個(gè)接口,本文主要介紹了Spring中PathMatcher路徑匹配器的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07
Spring Boot Admin監(jiān)控服務(wù)如何使用
這篇文章主要介紹了Spring Boot Admin監(jiān)控服務(wù)如何使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
通過(guò)實(shí)例解析java8中的parallelStream
這篇文章主要介紹了通過(guò)實(shí)例解析java8中的parallelStream,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
SpringBoot集成QQ第三方登陸的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot集成QQ第三方登陸的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
java?web項(xiàng)目Session獲取不到問(wèn)題及解決
這篇文章主要介紹了java?web項(xiàng)目Session獲取不到問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01

