『壹』 java 里static 和volatile的區別
變數放在主存區上,使用該變數的每個線程,都將從主存區拷貝一份到自己的工作區上進行操作。
volatile, 聲明這個欄位易變(可能被多個線程使用),Java內存模型負責各個線程的工作區與主存區的該欄位的值保持同步,即一致性。
static, 聲明這個欄位是靜態的(可能被多個實例共享),在主存區上該類的所有實例的該欄位為同一個變數,即唯一性。
volatile, 聲明變數值的一致性;static,聲明變數的唯一性。
此外,volatile同步機制不同於synchronized, 前者是內存同步,後者不僅包含內存同步(一致性),且保證線程互斥(互斥性)。
static 只是聲明變數在主存上的唯一性,不能保證工作區與主存區變數值的一致性;除非變數的值是不可變的,即再加上final的修飾符,否則static聲明的變數,不是線程安全的。
下面摘自Java語言規范(Java Language Specification)的官方解釋:
1) If a field is declared static, there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created.
2) A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable。
『貳』 java中的關鍵字是是什麼意思
abstract :表明類或類中的方法是抽象的;
assert :聲明斷言;
boolean :基本數據類型之一,布爾類型;
break :提前跳出一個塊;
byte :基本數據類型之一,位元組類型;
case :在switch語句中,表明其中的一個分支
catch :用於處理例外情況,用來捕捉異常;
char :基本數據類型之一,字元類型;
class :類;
continue :回到一個塊的開始處;
default :用在switch語句中,表明一個默認的分支;
do :用在「do while」循環結構中;
double :基本數據類型之一,雙精度浮點數類型;
else :在條件語句中,表明當條件不成立時的分支;
extends :用來表明一個類是另一個類的子類;
final :用來表明一個類不能派生出子類,或類中的方法不能被覆蓋,或聲明一個變數是常量;
finally :用於處理異常情況,用來聲明一個肯定會被執行到的塊;
float :基本數據類型之一,單精度浮點數類型;
for :一種循環結構的引導詞;
if :條件語句的引導詞;
implements :表明一個類實現了給定的介面;
import :表明要訪問指定的類或包;
instanceof :用來測試一個對象是否是一個指定的類的實例;
int :基本數據類型之一,整數類型;
interface :介面;
long :基本數據類型之一,長整數類型;
native :用來聲明一個方法是由與機器相關的語言(如C/C++/FORTRAN語言)實現的;
new :用來申請新的對象;
package :包;
private :一種訪問模式:私有模式;
protected :一種訪問模式:保護模式;
public :一種訪問模式:公共模式;
return :從方法中返回值;
short :基本數據類型之一,短整數類型;
static :表明域或方法是靜態的,即該域或方法是屬於類的;
strictfp :用來聲明FP-strict(雙精度或單精度浮點數)表達式;
算術規范:
super :當前對象的父類對象的引用;
switch :分支結構的引用詞;
synchronized :表明一段代碼的執行需要同步;
this :當前對象的引用;
throw :拋出一個異常;
throws :聲明方法中拋出的所有異常;
thansient :聲明不用序列化的域;
try :嘗試一個可能拋出異常的程序塊;
void :表明方法不返回值;
volatile :表明兩個或多個變數必須同步地發生變法;
while :用在循環結構中;
enum :聲明枚舉類型;
說明:
⑴Java的關鍵字也是隨新的版本發布在不斷變動中的,不是一成不變的。
⑵所有關鍵字都是小寫的。
⑶除這些關鍵字外,Java還定義了一些保留字,也就是說Java保留了它們,但是沒有使用它們,這些詞不 能作為標識符使用。
cast、goto、future、generic、inner、operator、outer、rest、var
⑷true和false不是關鍵字,而是boolean類型直接量。
⑸null也不是關鍵字。
⑹無sizeof運算符;所有類型的長度和表示是固定的,不依賴執行。
『叄』 java單例雙重檢查鎖為什麼需要加volatile關鍵字
已經修改,的確應該加上volatile關鍵字。不加的情況下,假設兩個線程,線專程A正在執行instance = new Instance()的操作,屬而線程B開始執行if(instance==null)的判斷,當不存在volatile的時候,因為 new Instance()是一個非原子操作,可能發生無序寫入,構造函數可能在整個對象構造完成前執行完畢,線程B可能會看到一個不完整的instance對象,因為java的某些實現會在內存中開辟一片存儲對象的區域後直接返回內存的引用,所以線程B判斷不為null,而這時候實際上,instance的構造函數還沒有執行,從而線程b得到不完整的對象。在 Instance 的構造函數執行之前,會在內存中開辟一片存儲對象的區域後直接返回內存的引用,賦值給變數 instance,instance也就可能成為非 null 的,即賦值語句在對象實例化之前調用,此時別的線程得到的是一個還會初始化的對象,這樣會導致系統崩潰線程B可能會看到一個不完整的instance對象,因為java的某些實現,所以線程B判斷不為null。從而得到不完整的對象。
『肆』 java編程,如何徹底理解volatile關鍵字
非java程序員,不過volatile在其他語言中也存在,簡單說下。
1,volatile只在多線程程序中有意義。
2,為了提高性能,編譯器工作時會進行一些優化,如指令排序,甚至跳過一些指令。如:
var
a=1;
a=2;
a=3;
編譯後的結果可能就只執行
a
=
3
3,程序運行時,普通變數會有緩存機制(如cpu緩存、線程本地緩存等),程序讀取時先從緩存讀取,所以多線程的程序運行時可能存在臟讀問題。即第一個線程已經修改了變數值,但第二個線程還在使用緩存中的舊數據。
volatile的作用就是告訴編譯器,不要對使用該變數的代碼進行優化,每次讀寫操作都訪問變數的原始數據。
『伍』 java中對象或者數組用volatile修飾有什麼用
就像大家更熟悉的const一樣,volatile是一個類型修飾符(type specifier)。它是被設計用來修飾被不同線程訪問和修改的變數。如果沒有volatile,基本上會導致這樣的結果:要麼無法編寫多線程程序,要麼編譯器失去大量優化的機會
volatile的作用是: 作為指令關鍵字,確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值.
『陸』 誰能真正整明白java volatile 關鍵字
四.深入剖析volatile關鍵字
在前面講述了很多東西,其實都是為講述volatile關鍵字作鋪墊,那麼接下來我們就進入主題。
1.volatile關鍵字的兩層語義
一旦一個共享變數(類的成員變數、類的靜態成員變數)被volatile修飾之後,那麼就具備了兩層語義:
1)保證了不同線程對這個變數進行操作時的可見性,即一個線程修改了某個變數的值,這新值對其他線程來說是立即可見的。
2)禁止進行指令重排序。
先看一段代碼,假如線程1先執行,線程2後執行:
這段代碼是很典型的一段代碼,很多人在中斷線程時可能都會採用這種標記辦法。但是事實上,這段代碼會完全運行正確么?即一定會將線程中斷么?不一定,也許在大多數時候,這個代碼能夠把線程中斷,但是也有可能會導致無法中斷線程(雖然這個可能性很小,但是只要一旦發生這種情況就會造成死循環了)。
下面解釋一下這段代碼為何有可能導致無法中斷線程。在前面已經解釋過,每個線程在運行過程中都有自己的工作內存,那麼線程1在運行的時候,會將stop變數的值拷貝一份放在自己的工作內存當中。
那麼當線程2更改了stop變數的值之後,但是還沒來得及寫入主存當中,線程2轉去做其他事情了,那麼線程1由於不知道線程2對stop變數的更改,因此還會一直循環下去。
但是用volatile修飾之後就變得不一樣了:
第一:使用volatile關鍵字會強制將修改的值立即寫入主存;
第二:使用volatile關鍵字的話,當線程2進行修改時,會導致線程1的工作內存中緩存變數stop的緩存行無效(反映到硬體層的話,就是CPU的L1或者L2緩存中對應的緩存行無效);
第三:由於線程1的工作內存中緩存變數stop的緩存行無效,所以線程1再次讀取變數stop的值時會去主存讀取。
那麼在線程2修改stop值時(當然這里包括2個操作,修改線程2工作內存中的值,然後將修改後的值寫入內存),會使得線程1的工作內存中緩存變數stop的緩存行無效,然後線程1讀取時,發現自己的緩存行無效,它會等待緩存行對應的主存地址被更新之後,然後去對應的主存讀取最新的值。
那麼線程1讀取到的就是最新的正確的值。
2.volatile保證原子性嗎?
從上面知道volatile關鍵字保證了操作的可見性,但是volatile能保證對變數的操作是原子性嗎?
下面看一個例子:
大家想一下這段程序的輸出結果是多少?也許有些朋友認為是10000。但是事實上運行它會發現每次運行結果都不一致,都是一個小於10000的數字。
可能有的朋友就會有疑問,不對啊,上面是對變數inc進行自增操作,由於volatile保證了可見性,那麼在每個線程中對inc自增完之後,在其他線程中都能看到修改後的值啊,所以有10個線程分別進行了1000次操作,那麼最終inc的值應該是1000*10=10000。
這裡面就有一個誤區了,volatile關鍵字能保證可見性沒有錯,但是上面的程序錯在沒能保證原子性。可見性只能保證每次讀取的是最新的值,但是volatile沒辦法保證對變數的操作的原子性。
在前面已經提到過,自增操作是不具備原子性的,它包括讀取變數的原始值、進行加1操作、寫入工作內存。那麼就是說自增操作的三個子操作可能會分割開執行,就有可能導致下面這種情況出現:
假如某個時刻變數inc的值為10,
線程1對變數進行自增操作,線程1先讀取了變數inc的原始值,然後線程1被阻塞了;
然後線程2對變數進行自增操作,線程2也去讀取變數inc的原始值,由於線程1隻是對變數inc進行讀取操作,而沒有對變數進行修改操作,所以不會導致線程2的工作內存中緩存變數inc的緩存行無效,所以線程2會直接去主存讀取inc的值,發現inc的值時10,然後進行加1操作,並把11寫入工作內存,最後寫入主存。
然後線程1接著進行加1操作,由於已經讀取了inc的值,注意此時在線程1的工作內存中inc的值仍然為10,所以線程1對inc進行加1操作後inc的值為11,然後將11寫入工作內存,最後寫入主存。
那麼兩個線程分別進行了一次自增操作後,inc只增加了1。
解釋到這里,可能有朋友會有疑問,不對啊,前面不是保證一個變數在修改volatile變數時,會讓緩存行無效嗎?然後其他線程去讀就會讀到新的值,對,這個沒錯。這個就是上面的happens-before規則中的volatile變數規則,但是要注意,線程1對變數進行讀取操作之後,被阻塞了的話,並沒有對inc值進行修改。然後雖然volatile能保證線程2對變數inc的值讀取是從內存中讀取的,但是線程1沒有進行修改,所以線程2根本就不會看到修改的值。
根源就在這里,自增操作不是原子性操作,而且volatile也無法保證對變數的任何操作都是原子性的。
把上面的代碼改成以下任何一種都可以達到效果:
採用synchronized:
採用Lock:
public class Test {public int inc = 0;Lock lock = new ReentrantLock();public void increase() {lock.lock();try {inc++;} finally{lock.unlock();}}public static void main(String[] args) {final Test test = new Test();for(int i=0;i<10;i++){new Thread(){public void run() {for(int j=0;j<1000;j++)test.increase();};}.start();}while(Thread.activeCount()>1) //保證前面的線程都執行完Thread.yield();System.out.println(test.inc);}}採用AtomicInteger:
前面舉這個例子的時候,提到有可能語句2會在語句1之前執行,那麼久可能導致context還沒被初始化,而線程2中就使用未初始化的context去進行操作,導致程序出錯。
這里如果用volatile關鍵字對inited變數進行修飾,就不會出現這種問題了,因為當執行到語句2時,必定能保證context已經初始化完畢。
4.volatile的原理和實現機制
前面講述了源於volatile關鍵字的一些使用,下面我們來探討一下volatile到底如何保證可見性和禁止指令重排序的。
下面這段話摘自《深入理解Java虛擬機》:
「觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的匯編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令」
lock前綴指令實際上相當於一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:
1)它確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;
2)它會強制將對緩存的修改操作立即寫入主存;
3)如果是寫操作,它會導致其他CPU中對應的緩存行無效。