Java volatile關(guān)鍵字特性講解上篇
一、概述
volatile是Java中的關(guān)鍵字,用來(lái)修飾會(huì)被不同線程訪問(wèn)和修改的變量。
volatile是Java虛擬機(jī)提供的輕量級(jí)的同步機(jī)制,它有三個(gè)特性:
(1)保證可見(jiàn)性
(2)不保證原子性
(3)禁止指令重排
二、特性詳解
volatile保證可見(jiàn)性
Java內(nèi)存模型(JMM)定義了一組規(guī)則、規(guī)范,規(guī)定了程序中各個(gè)變量的訪問(wèn)方法。JMM關(guān)于同步的規(guī)定:
(1)線程解鎖前,必須把共享變量的值刷新回主內(nèi)存;
(2)線程加鎖前,必須讀取主內(nèi)存的最新值同步到自己的工作內(nèi)存;
(3)加鎖解鎖必須是同一把鎖;
說(shuō)明:由于JVM運(yùn)行程序的實(shí)體是線程,創(chuàng)建每個(gè)線程時(shí),JMM會(huì)為其創(chuàng)建一個(gè)工作內(nèi)存(也稱(chēng)??臻g),工作內(nèi)存是每個(gè)線程的私有數(shù)據(jù)區(qū)域。
Java內(nèi)存模型規(guī)定所有變量都存儲(chǔ)在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問(wèn)。
但是線程對(duì)變量的操作(讀取、賦值等)必須在工作內(nèi)存中進(jìn)行。因此首先要將變量從主內(nèi)存拷貝到自己的工作內(nèi)存,然后對(duì)變量進(jìn)行操作,操作完成后再將變量寫(xiě)會(huì)主內(nèi)存中。
舉例說(shuō)明:
(1)火車(chē)票賣(mài)票系統(tǒng)還剩下一張票,并已經(jīng)刷入到主內(nèi)存中:ticketNum = 1;
(2)此時(shí)有3個(gè)用戶(hù)在同時(shí)購(gòu)買(mǎi)票,3個(gè)線程都讀入了目前的票數(shù),ticketNum=1,那么線程就會(huì)繼續(xù)進(jìn)入購(gòu)買(mǎi)流程。
(3)假設(shè)其中一個(gè)線程先搶占了CPU資源,先買(mǎi)到票,并將自己的工作內(nèi)存中的ticketNum值改為0,ticketNum=0,然后再寫(xiě)回到主內(nèi)存。
這時(shí),由于一個(gè)線程的用戶(hù)已經(jīng)買(mǎi)到了票,那么其他用戶(hù)的線程應(yīng)該不能再繼續(xù)進(jìn)入購(gòu)買(mǎi)票的流程了,因此需要系統(tǒng)通知到其他線程 ticketNum=0 這個(gè)消息。如果可以達(dá)到這樣的效果,可以理解為 具有可見(jiàn)性。
無(wú)可見(jiàn)性代碼演示:
@Test
public void test1() {
DataDemo dataDemo = new DataDemo();
RunThread runThread = new RunThread(dataDemo);
runThread.start();
while (dataDemo.getNumber() == 0) {
}
System.out.println("具有可見(jiàn)性驗(yàn)證通過(guò)");
}
public class DataDemo {
private int number = 0;
public void add() {
this.number = this.number + 10;
}
public int getNumber() {
return number;
}
}
public class RunThread extends Thread {
private DataDemo dataDemo;
public RunThread(DataDemo dataDemo) {
this.dataDemo = dataDemo;
}
@Override
public void run() {
System.out.println("線程[" + Thread.currentThread().getName() + "] 正在執(zhí)行");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
dataDemo.add();
System.out.println("線程[" + Thread.currentThread().getName() + "]更新后,number值為:" + dataDemo.getNumber());
}
}執(zhí)行結(jié)果:
線程[Thread-0] 正在執(zhí)行
線程[Thread-0]更新后,number值為:10

結(jié)果分析:
可以看出子線程啟動(dòng)后將number值改為了10,雖然已經(jīng)改為了非0,但是主線程仍然一直處于while循環(huán)中,因此此時(shí)number不具有可見(jiàn)性,系統(tǒng)不會(huì)主動(dòng)通知主線程number值修改。
原理說(shuō)明:
這個(gè)問(wèn)題其實(shí)就是私有堆棧中的值和公共堆棧中的值不同步造成的。解決這樣的問(wèn)題就要使用 volatile 關(guān)鍵字了,它主要的作用就是當(dāng)線程訪問(wèn)number這個(gè)變量時(shí),強(qiáng)制性從公共堆棧中進(jìn)行取值。

可見(jiàn)性代碼演示:
@Test
public void test1() {
DataDemo dataDemo = new DataDemo();
RunThread runThread = new RunThread(dataDemo);
runThread.start();
while (dataDemo.getNumber() == 0) {
}
System.out.println("具有可見(jiàn)性驗(yàn)證通過(guò)");
}
public class DataDemo {
// 給變量 number 添加 volatile 關(guān)鍵字修飾
volatile private int number = 0;
public void add() {
this.number = this.number + 10;
}
public int getNumber() {
return number;
}
}
public class RunThread extends Thread {
private DataDemo dataDemo;
public RunThread(DataDemo dataDemo) {
this.dataDemo = dataDemo;
}
@Override
public void run() {
System.out.println("線程[" + Thread.currentThread().getName() + "] 正在執(zhí)行");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
dataDemo.add();
System.out.println("線程[" + Thread.currentThread().getName() + "]更新后,number值為:" + dataDemo.getNumber());
}
}執(zhí)行結(jié)果:
線程[Thread-0] 正在執(zhí)行
線程[Thread-0]更新后,number值為:10
具有可見(jiàn)性驗(yàn)證通過(guò)
結(jié)果分析:
通過(guò)對(duì)變量number變量添加了volatile關(guān)鍵字修飾,可以看出子線程啟動(dòng)后將number值改為了10,隨后主線程跳出了while循環(huán),輸出了“具有可見(jiàn)性驗(yàn)證通過(guò)”,說(shuō)明此時(shí)number具有可見(jiàn)性。
原理說(shuō)明:
通過(guò)使用 volatile 關(guān)鍵字,強(qiáng)制從公共內(nèi)存中讀取變量的值,內(nèi)存結(jié)構(gòu)如圖:

到此這篇關(guān)于Java volatile關(guān)鍵字特性講解上篇的文章就介紹到這了,更多相關(guān)Java volatile內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決spring-data-jpa 事物中修改屬性自動(dòng)更新update問(wèn)題
這篇文章主要介紹了解決spring-data-jpa 事物中修改屬性自動(dòng)更新update問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家2021-08-08
SpringBoot實(shí)現(xiàn)對(duì)Http接口進(jìn)行監(jiān)控的代碼
Spring Boot Actuator是Spring Boot提供的一個(gè)模塊,用于監(jiān)控和管理Spring Boot應(yīng)用程序的運(yùn)行時(shí)信息,本文將介紹一下Spring Boot Actuator以及代碼示例,以及如何進(jìn)行接口請(qǐng)求監(jiān)控,需要的朋友可以參考下2024-07-07
java?Springboot對(duì)接開(kāi)發(fā)微信支付詳細(xì)流程
最近要做一個(gè)微信小程序,需要微信支付,所以研究了下怎么在java上集成微信支付功能,下面這篇文章主要給大家介紹了關(guān)于java?Springboot對(duì)接開(kāi)發(fā)微信支付的相關(guān)資料,需要的朋友可以參考下2024-08-08
springboot整合swagger3和knife4j的詳細(xì)過(guò)程
knife4j的前身是swagger-bootstrap-ui,取名knife4j是希望她能像一把匕首一樣小巧,輕量,并且功能強(qiáng)悍,下面這篇文章主要介紹了springboot整合swagger3和knife4j的詳細(xì)過(guò)程,需要的朋友可以參考下2022-11-11
java實(shí)現(xiàn)ModbusCRC16校驗(yàn)的示例代碼
本文介紹了使用Java實(shí)現(xiàn)ModbusCRC16校驗(yàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
Postman實(shí)現(xiàn)傳List<String>集合
這篇文章主要介紹了Postman實(shí)現(xiàn)傳List<String>集合方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08

