Java中關於原子操作和volatile關鍵字
研究ThreadPoolExecutor的時候,發現其中大量使用了volatile變數。不知為何,因此做了一番查詢,研究: 其中借鑑了很多網上資料。 在瞭解volatile變數作用前,先需要明白一些概念:
什麼是原子操作?
所謂原子操作,就是"不可中斷的一個或一系列操作" , 在確認一個操作是原子的情況下,多執行緒環境裡面,我們可以避免僅僅為保護這個操作在外圍加上效能昂貴的鎖,甚至藉助於原子操作,我們可以實現互斥鎖。 很多作業系統都為int型別提供了+-賦值的原子操作版本,比如 NT 提供了 InterlockedExchange 等API, Linux/UNIX也提供了atomic_set 等函式。
關於java中的原子性?
原子性可以應用於除long和double之外的所有基本型別之上的“簡單操作”。對於讀取和寫入出long double之外的基本型別變數這樣的操作,可以保證它們會被當作不可分(原子)的操作來操作。 因為JVM的版本和其它的問題,其它的很多操作就不好說了,比如說++操作在C++中是原子操作,但在Java中就不好說了。 另外,Java提供了AtomicInteger等原子類。再就是用原子性來控制併發比較麻煩,也容易出問題。
volatile原理是什麼?
Java中volatile關鍵字原義是“不穩定、變化”的意思
使用volatile和不使用volatile的區別在於JVM記憶體主存和執行緒工作記憶體的同步之上。volatile
其實是告訴處理器, 不要將我放入工作記憶體, 請直接在主存操作我.
接下來是測試 :(通過測試能更好的發現和分析問題)
申明瞭幾種整形的變數,開啟100個執行緒同時對這些變數進行++操作,發現結果差異很大:
>>Execute End:
>>Atomic: 100000
>>VInteger: 38790
>>Integer: 68749
>>Source i: 99205
>>Source Vi: 99286
也就是說除了Atomic,其他的都是錯誤的。
我們通過一些疑問,來解釋一下。
1:為什麼會產生錯誤的資料?
多執行緒引起的,因為對於多執行緒同時操作一個整型變數在大併發操作的情況下無法做到同步,而Atom提供了很多針對此類執行緒安全問題的解決方案,因此解決了同時讀寫操作的問題。
2:為什麼會造成同步問題?
Java多執行緒在對變數進行操作的時候,實際上是每個執行緒會單獨分配一個針對i值的拷貝(獨立記憶體區域),但是申明的i值確是在主記憶體區域中,當對i值修改完畢後,執行緒會將自己記憶體區域塊中的i值拷貝到主記憶體區域中,因此有可能每個執行緒拿到的i值是不一樣的,從而出現了同步問題。
3:為什麼使用volatile修飾integer變數後,還是不行?
因為volatile僅僅只是解決了儲存的問題,即i值只是保留在了一個記憶體區域中,但是i++這個操作,涉及到獲取i值、修改i值、儲存i值(i=i+1),這裡的volatile只是解決了儲存i值得問題,至於獲取和修改i值,確是沒有做到同步。
4:既然不能做到同步,那為什麼還要用volatile這種修飾符?
主要的一個原因是方便,因為只需新增一個修飾符即可,而無需做物件加鎖、解鎖這麼麻煩的操作。但是本人不推薦使用這種機制,因為比較容易出問題(髒資料),而且也保證不了同步。
5:那到底如何解決這樣的問題?
第一種:採用同步synchronized解決,這樣雖然解決了問題,但是也降低了系統的效能。
第二種:採用原子性資料Atomic變數,這是從JDK1.5開始才存在的針對原子性的解決方案,這種方案也是目前比較好的解決方案了。
6:Atomic的實現基本原理?
首先Atomic中的變數是申明為了volatile變數的,這樣就保證的變數的儲存和讀取是一致的,都是來自同一個記憶體塊,然後Atomic提供了getAndIncrement方法,該方法對變數的++操作進行了封裝,並提供了compareAndSet方法,來完成對單個變數的加鎖和解鎖操作,方法中用到了一個UnSafe的物件,現在還不知道這個UnSafe的工作原理(似乎沒有公開原始碼)。Atomic雖然解決了同步的問題,但是效能上面還是會有所損失,不過影響不大,網上有針對這方面的測試,大概50million的操作對比是250ms
: 850ms,對於大部分的高效能應用,應該還是夠的了。
package qflag.ucstar.test.thread;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 測試原子性的同步
* @author sigangjun 2015-3-14
*
*/
public class TestAtomic {
public static AtomicInteger astom_i = new AtomicInteger();
public static volatile Integer v_integer_i = 0;
public static volatile int v_i = 0;
public static Integer integer_i = 0;
public static int i = 0;
public static int endThread = 0;
public static void main(String[] args) {
new TestAtomic().testAtomic();
}
public void testAtomic() {
for(int i=0; i<100; i++) {
new Thread(new IntegerTestThread()).start();
}
try {
for(;;) {
Thread.sleep(500);
if(TestAtomic.endThread == 100) {
System.out.println(">>Execute End:");
System.out.println(">>Atomic: /t"+TestAtomic.astom_i);
System.out.println(">>VInteger: /t"+TestAtomic.v_integer_i);
System.out.println(">>Integer: /t"+TestAtomic.integer_i);
System.out.println(">>Source i: /t"+TestAtomic.i);
System.out.println(">>Source Vi: /t"+TestAtomic.v_i);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class IntegerTestThread implements Runnable {
public void run() {
int x = 0;
while(x<1000) {
TestAtomic.astom_i.incrementAndGet();
TestAtomic.v_integer_i++;
TestAtomic.integer_i++;
TestAtomic.i++;
TestAtomic.v_i++;
x++;
}
++TestAtomic.endThread; //貌似很無敵!難道是原子性的嗎?
}
}
-----------------------------------------xx-----------------------------------xx-----------------------------------------------
本人繼續補充:
除了TestAtomic.endThread,其他的變數都被忽略了。具體解釋可參見注釋。
import java.util.concurrent.atomic.AtomicInteger;
import java.io.*;
/**
* 測試原子性的同步
* @author sigangjun 2015-3-29
*
*/
public class TestAtomic {
public static final int N=10;
public static final int M=10000;
public static int perfect_result=M*N;
public static int endThread = 0;
private PrintWriter out;//將資訊輸入至文字"out.txt",因為控制檯buffer可能不夠.
public TestAtomic() throws IOException
{
out =new PrintWriter(
new BufferedWriter(
new FileWriter("out.txt")));
}
public static void main(String[] args) {
try{
new TestAtomic().testAtomic();
}catch(Exception e){
System.out.println(e.getMessage());
}
System.out.println("OK./nStatistical report:");
System.out.println("Covered by "+(perfect_result-endThread)+" times.");
}
public void testAtomic() {
Thread[] td=new Thread[N];
for(int i=0; i<N; i++) {
td[i]=new Thread(new IntegerTestThread(i+1));
}
for(int i=0; i<N; i++) {
td[i].start();
out.println((i+1)+" go..") ; //此處如果run()方法程式碼少,立即可觀察到complete完成資訊。
}
try {
long temp=0; //存放了上次的endTread值。
int count=1000; //如果temp值超過一千次的重複就可以認為結束程式。
for(;;) {
//Thread.sleep(1); //有可能main執行緒執行過快,可以調節取樣的頻率。
if(TestAtomic.endThread == perfect_result) {
out.println("==============/r/nPerfect!/r/n=============="); //完美匹配!
break;
}
if(temp==TestAtomic.endThread){
out.println("Equal!!");//有重複,有可能是所有執行緒執行結束時的重複,也有可能是main執行緒取樣過快。
count--;//倒計時中。。。
}
else {
temp=TestAtomic.endThread;//給temp賦新值。
count=1000;//重新設定倒計時。
}
out.println("endThread = "+TestAtomic.endThread);//在此處有機率可觀察當前的endThread值比上次要少。
//這是關鍵之處!
if(count<=0)
{
out.println("/r/nI'll be crazy if I wait for that once again!/r/nFailed, OMG!+_+");
break;
}
}
out.close();
}catch(Exception e) {
e.printStackTrace();
}
}
class IntegerTestThread implements Runnable {
private int id;
public IntegerTestThread(int i){
this.id=i;
}
public void run() {
int i=M;//充分保證執行緒重疊執行
while(i>0){
try{
//Thread.sleep((int)(10*Math.random()));//設定睡眠時間,從而儘可能使執行緒重疊執行。
}catch(Exception e){
++TestAtomic.endThread;//測試該語句的“原子”性。其實做完實驗,我們知道,++i,i++, i=i+1一樣都不能保證原子性。
//我們可以從最終的endThread值是不是等於M*N得知。
i--;
}
out.println("************/r/n"+id+" has Completed!/r/n************/r/n") ;
}
}
}