java 原子锁
研究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,對于大部分的高性能應用,應該還是夠的了。?
Java代碼?? package?qflag.ucstar.test.thread;?? ?? import?java.util.concurrent.atomic.AtomicInteger;?? ?? /**? ?*?測試原子性的同步? ?*?@author?polarbear?2009-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;?? ????}?? } ?
不知為何,因此做了一番查找,研究:?
其中借鑒了很多網上資料。?
在了解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,對于大部分的高性能應用,應該還是夠的了。?
Java代碼??
總結
- 上一篇: 云服务器搭建百度贴吧自动签到(贴吧云签到
- 下一篇: 人生的诗意