亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Java多線程-volatile的原理與技巧

系統(tǒng) 1748 0

volatile, 用更低的代價替代同步

為什么 使用volatile比同步代價更低?
同步的代價, 主要由其覆蓋范圍決定, 如果可以降低同步的覆蓋范圍, 則可以大幅提升程序性能.?

而volatile的覆蓋范圍僅僅變量級別的. 因此它的同步代價很低.

volatile原理是什么?
volatile的語義, 其實是告訴處理器, 不要將我放入工作內(nèi)存, 請直接在主存操作我.(工作內(nèi)存詳見java內(nèi)存模型)

因此, 當(dāng)多核或多線程在訪問該變量時, 都將直接
操作 主存, 這從本質(zhì)上, 做到了變量共享.

volatile的有什么優(yōu)勢?
1, 更大的程序吞吐量
2, 更少的代碼實現(xiàn)多線程
3, 程序的伸縮性較好
4, 比較好理解, 無需太高的學(xué)習(xí)成本

volatile有什么劣勢?
1, 容易出問題
2, 比較難設(shè)計

?


?

在java線程并發(fā)處理中,有一個關(guān)鍵字volatile的使用目前存在很大的混淆,以為使用這個關(guān)鍵字,在進(jìn)行多線程并發(fā)處理的時候就可以萬事大吉。

?

Java語言是支持多線程的,為了解決線程并發(fā)的問題,在語言內(nèi)部引入了 同步塊 和 volatile 關(guān)鍵字機制。

?

?

?

synchronized ?

?

同步塊大家都比較熟悉,通過 synchronized 關(guān)鍵字來實現(xiàn),所有加上synchronized 和 塊語句,在多線程訪問的時候,同一時刻只能有一個線程能夠用

?

synchronized 修飾的方法 或者 代碼塊。

?

volatile

?

用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改后的最的值。volatile很容易被誤用,用來進(jìn)行原子性操作。

?

下面看一個例子,我們實現(xiàn)一個計數(shù)器,每次線程啟動的時候,會調(diào)用計數(shù)器inc方法,對計數(shù)器進(jìn)行加一

?執(zhí)行環(huán)境——jdk版本:jdk1.6.0_31 ,內(nèi)存 :3G?? cpu:x86 2.4G

1 package linkedList2013;
2
3 public class Counter {
4
5 ???? public static int count = 0 ;
6
7 ???? public static void inc() {
8
9 ???????? // 這里延遲1毫秒,使得結(jié)果明顯
10 ???????? try {
11 ???????????? Thread.sleep(1 );
12 ???????? } catch (InterruptedException e) { 13 ??????? }
14
15 ???????? count++ ;
16 ??? }
17
18 ???? public static void main(String[] args) {
19
20 ???????? // 同時啟動1000個線程,去進(jìn)行i++計算,看看實際結(jié)果
21
22 ???????? for ( int i = 0; i < 1000; i++ ) {
23 ???????????? new Thread( new Runnable() {
24 ??????????????? @Override
25 ???????????????? public void run() {
26 ??????????????????? Counter.inc();
27 ??????????????? }
28 ??????????? }).start();
29 ??????? }
30
31 ???????? // 這里每次運行的值都有可能不同,可能為1000
32 ???????? System.out.println("運行結(jié)果:Counter.count=" + Counter.count);
33 ??? }
34 }

運行結(jié)果:Counter.count=995 ?
?實際運算結(jié)果每次可能都不一樣,本機的結(jié)果為:運行結(jié)果:Counter.count=995,可以看出,在多線程的環(huán)境下,Counter.count并沒有期望結(jié)果是1000 ?
?
很多人以為,這個是多線程并發(fā)問題,只需要在變量count之前加上volatile就可以避免這個問題,那我們在修改代碼看看,看看結(jié)果是不是符合我們的期望

?

1 package linkedList2013;
2
3 public class Counter {
4
5 ???? public volatile static int count = 0 ;
6
7 ???? public static void inc() {
8
9 ???????? // 這里延遲1毫秒,使得結(jié)果明顯
10 ???????? try {
11 ???????????? Thread.sleep(1 );
12 ???????? } catch (InterruptedException e) { 13 ??????? }
14
15 ???????? count++ ;
16 ??? }
17
18 ???? public static void main(String[] args) {
19
20 ???????? // 同時啟動1000個線程,去進(jìn)行i++計算,看看實際結(jié)果
21
22 ???????? for ( int i = 0; i < 1000; i++ ) {
23 ???????????? new Thread( new Runnable() {
24 ??????????????? @Override
25 ???????????????? public void run() {
26 ??????????????????? Counter.inc();
27 ??????????????? }
28 ??????????? }).start();
29 ??????? }
30
31 ???????? // 這里每次運行的值都有可能不同,可能為1000
32 ???????? System.out.println("運行結(jié)果:Counter.count=" + Counter.count);
33 ??? }
34 }

運行結(jié)果:Counter.count=992

運行結(jié)果還是沒有我們期望的1000,下面我們分析一下原因

?

?

?

在 java 垃圾回收整理一文中,描述了jvm運行時刻內(nèi)存的分配。其中有一個內(nèi)存區(qū)域是jvm虛擬機棧,每一個線程運行時都有一個線程棧,

?

線程棧保存了線程運行時候變量值信息。當(dāng)線程訪問某一個對象時候值的時候,首先通過對象的引用找到對應(yīng)在堆內(nèi)存的變量的值,然后把堆內(nèi)存

?

變量的具體值load到線程本地內(nèi)存中,建立一個變量副本,之后線程就不再和對象在堆內(nèi)存變量值有任何關(guān)系,而是直接修改副本變量的值,

?

在修改完之后的某一個時刻(線程退出之前),自動把線程變量副本的值回寫到對象在堆中變量。這樣在堆中的對象的值就產(chǎn)生變化了。?

?

read and load 從主存復(fù)制變量到當(dāng)前工作內(nèi)存
use and assign? 執(zhí)行代碼,改變共享變量值 ?
store and write 用工作內(nèi)存數(shù)據(jù)刷新主存相關(guān)內(nèi)容

?

其中use and assign 可以多次出現(xiàn)

?

但是這一些操作并不是原子性,也就是 在read load之后,如果主內(nèi)存count變量發(fā)生修改之后,線程工作內(nèi)存中的值由于已經(jīng)加載,不會產(chǎn)生對應(yīng)的變化,所以計算出來的結(jié)果會和預(yù)期不一樣

?

對于volatile修飾的變量,jvm虛擬機只是保證從主內(nèi)存加載到線程工作內(nèi)存的值是最新的

?

例如假如線程1,線程2 在進(jìn)行read,load 操作中,發(fā)現(xiàn)主內(nèi)存中count的值都是5,那么都會加載這個最新的值

?

在線程1堆count進(jìn)行修改之后,會write到主內(nèi)存中,主內(nèi)存中的count變量就會變?yōu)?

?

線程2由于已經(jīng)進(jìn)行read,load操作,在進(jìn)行運算之后,也會更新主內(nèi)存count的變量值為6

?

導(dǎo)致兩個線程及時用volatile關(guān)鍵字修改之后,還是會存在并發(fā)的情況。

?

?

如何避免這種情況?
解決以上問題的方法:
一種是 操作時, 加上同步.
這種方法, 無疑將大大降低程序性能, 且違背了volatile的初衷.

第二種方式是, 使用硬件原語(CAS), 實現(xiàn)非阻塞算法
從CPU原語上,? 支持變量級別的低開銷同步.

CPU原語-比較并交換(CompareAndSet),實現(xiàn)非阻塞算法

什么是CAS?
cas是現(xiàn)代CPU提供給并發(fā)程序使用的原語操作. 不同的CPU有不同的使用規(guī)范.

在 Intel 處理器中,比較并交換通過指令的 cmpxchg 系列實現(xiàn)。
PowerPC 處理器有一對名為“加載并保留”和“條件存儲”的指令,它們實現(xiàn)相同的目地;
MIPS 與 PowerPC 處理器相似,除了第一個指令稱為“加載鏈接”。

CAS 操作包含三個操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)

什么是非阻塞算法?
一個線程的失敗或掛起不應(yīng)該影響其他線程的失敗或掛起.這類算法稱之為非阻塞(nonblocking)算法

對比阻塞算法:
如果有一類并發(fā)操作, 其中一個線程優(yōu)先得到對象監(jiān)視器的鎖, 當(dāng)其他線程到達(dá)同步邊界時, 就會被阻塞.
直到前一個線程釋放掉鎖后, 才可以繼續(xù)競爭對象鎖.(當(dāng)然,這里的競爭也可是公平的, 按先來后到的次序)

CAS 原理:

我認(rèn)為位置 V 應(yīng)該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現(xiàn)在的值即可。

CAS使用示例(jdk 1.5 并發(fā)包 AtomicInteger類分析:)

1 /**
2 ???? * Atomically sets to the given value and returns the old value.
3 ???? *
4 ???? * @param newValue the new value
5 ???? * @return the previous value
6 ????? */
7 ???? public final int getAndSet( int newValue) {
8 ???????? for (;;) {
9 ???????????? int current = get();
10 ???????????? if (compareAndSet(current, newValue))
11 ???????????????? return current;
12 ??????? }
13 ??? }
14
15 ???? public final boolean compareAndSet( int expect, int update) {
16 ???????? return unsafe.compareAndSwapInt( this , valueOffset, expect, update);
17 ???? }

?

?


???

這個方法是, AtomicInteger類的常用方法, 作用是, 將變量設(shè)置為指定值, 并返回設(shè)置前的值.
它利用了cpu原語compareAndSet來保障值的唯一性.

另, AtomicInteger類中, 其他的實用方法, 也是基于同樣的實現(xiàn)方式.
比如 getAndIncrement, getAndDecrement, getAndAdd等等.

CAS語義上存在的 "
ABA 問題"

什么是ABA問題?
假設(shè), 第一次讀取V地址的A值, 然后通過CAS來判斷V地址的值是否仍舊為A, 如果是, 就將B的值寫入V地址,覆蓋A值.

但是, 語義上, 有一個漏洞, 當(dāng)?shù)谝淮巫x取V的A值, 此時, 內(nèi)存V的值變?yōu)锽值, 然后在未執(zhí)行CAS前, 又變回了A值.
此時, CAS再執(zhí)行時, 會判斷其正確的, 并進(jìn)行賦值.

這種判斷值的方式來斷定內(nèi)存是否被修改過, 針對某些問題, 是不適用的. ?

為了解決這種問題, jdk 1.5并發(fā)包提供了 AtomicStampedReference (有標(biāo)記的原子引用)類, 通過控制變量值的版本來保證CAS正確性.

其實, 大部分通過值的變化來CAS, 已經(jīng)夠用了.



jdk1.5原子包介紹(基于volatile)

包的特色:
1, 普通原子數(shù)值類型AtomicInteger, AtomicLong提供一些原子操作的加減運算.

2, 使用了解決臟數(shù)據(jù)問題的經(jīng)典模式-"比對后設(shè)定", 即 查看主存中數(shù)據(jù)是否與預(yù)期提供的值一致,如果一致,才更新.

3, 使用AtomicReference可以實現(xiàn)對所有對象的原子引用及賦值.包括Double與Float,
但不包括對其的計算.浮點的計算,只能依靠同步關(guān)鍵字或Lock接口來實現(xiàn)了.

4, 對數(shù)組元素里的對象,符合以上特點的, 也可采用原子操作.包里提供了一些數(shù)組原子操作類
AtomicIntegerArray, AtomicLongArray等等.

5, 大幅度提升系統(tǒng)吞吐量及性能.

Java多線程-volatile的原理與技巧


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 亚洲破处视频 | 成人嫩草研究院永久网址 | 婷婷在线网 | 美女在线看永久免费网址 | 亚洲精品视频网 | 26uuu另类欧美亚洲曰本 | 欧美性性性性性色大片 | 精品的一区二区三区 | 奇米影视盒7777 | 亚洲精品免费观看 | 日韩欧美综合在线二区三区 | 亚洲国产精品日韩在线观看 | 久久久久久久国产a∨ | 男人天堂网在线视频 | 最新国产在线视频 | 一区二区三区鲁丝不卡麻豆 | 久久黄色免费 | 96精彩视频在线观看 | 五月婷婷精品 | 亚洲精品第一页中文字幕 | 嫩草视频在线观看 | 国产精品免费aⅴ片在线观看 | 美美女高清毛片视频黄的一免费 | 国产一级特黄老妇女大片免费 | 婷婷国产成人久久精品激情 | 日韩 视频在线播放 | 人人干夜夜操 | 亚洲一区二区三区日本久久九 | 99re在线 | 国产亚洲人成a在线v网站 | 国产一级毛片外aaaa | 久久精品只有这里有 | 成年女人免费看一级人体片 | 免费一区二区三区四区五区 | 在线a毛片免费视频观看 | 国色天香成人网 | 久久精品国产色蜜蜜麻豆 | 亚洲视频在线观看免费视频 | 久久人人精品 | 日本一区中文字幕 | 成人影院免费在线观看 |