日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

Java多線(xiàn)程-volatile的原理與技巧

系統(tǒng) 1914 0

volatile, 用更低的代價(jià)替代同步

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

而volatile的覆蓋范圍僅僅變量級(jí)別的. 因此它的同步代價(jià)很低.

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

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

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

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

?


?

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

?

Java語(yǔ)言是支持多線(xiàn)程的,為了解決線(xiàn)程并發(fā)的問(wèn)題,在語(yǔ)言?xún)?nèi)部引入了 同步塊 和 volatile 關(guān)鍵字機(jī)制。

?

?

?

synchronized ?

?

同步塊大家都比較熟悉,通過(guò) synchronized 關(guān)鍵字來(lái)實(shí)現(xiàn),所有加上synchronized 和 塊語(yǔ)句,在多線(xiàn)程訪(fǎng)問(wèn)的時(shí)候,同一時(shí)刻只能有一個(gè)線(xiàn)程能夠用

?

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

?

volatile

?

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

?

下面看一個(gè)例子,我們實(shí)現(xiàn)一個(gè)計(jì)數(shù)器,每次線(xiàn)程啟動(dòng)的時(shí)候,會(huì)調(diào)用計(jì)數(shù)器inc方法,對(duì)計(jì)數(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 ???????? // 同時(shí)啟動(dòng)1000個(gè)線(xiàn)程,去進(jìn)行i++計(jì)算,看看實(shí)際結(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 ???????? // 這里每次運(yùn)行的值都有可能不同,可能為1000
32 ???????? System.out.println("運(yùn)行結(jié)果:Counter.count=" + Counter.count);
33 ??? }
34 }

運(yùn)行結(jié)果:Counter.count=995 ?
?實(shí)際運(yùn)算結(jié)果每次可能都不一樣,本機(jī)的結(jié)果為:運(yùn)行結(jié)果:Counter.count=995,可以看出,在多線(xiàn)程的環(huán)境下,Counter.count并沒(méi)有期望結(jié)果是1000 ?
?
很多人以為,這個(gè)是多線(xiàn)程并發(fā)問(wèn)題,只需要在變量count之前加上volatile就可以避免這個(gè)問(wèn)題,那我們?cè)谛薷拇a看看,看看結(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 ???????? // 同時(shí)啟動(dòng)1000個(gè)線(xiàn)程,去進(jìn)行i++計(jì)算,看看實(shí)際結(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 ???????? // 這里每次運(yùn)行的值都有可能不同,可能為1000
32 ???????? System.out.println("運(yùn)行結(jié)果:Counter.count=" + Counter.count);
33 ??? }
34 }

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

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

?

?

?

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

?

線(xiàn)程棧保存了線(xiàn)程運(yùn)行時(shí)候變量值信息。當(dāng)線(xiàn)程訪(fǎng)問(wèn)某一個(gè)對(duì)象時(shí)候值的時(shí)候,首先通過(guò)對(duì)象的引用找到對(duì)應(yīng)在堆內(nèi)存的變量的值,然后把堆內(nèi)存

?

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

?

在修改完之后的某一個(gè)時(shí)刻(線(xiàn)程退出之前),自動(dòng)把線(xiàn)程變量副本的值回寫(xiě)到對(duì)象在堆中變量。這樣在堆中的對(duì)象的值就產(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ā)生修改之后,線(xiàn)程工作內(nèi)存中的值由于已經(jīng)加載,不會(huì)產(chǎn)生對(duì)應(yīng)的變化,所以計(jì)算出來(lái)的結(jié)果會(huì)和預(yù)期不一樣

?

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

?

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

?

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

?

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

?

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

?

?

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

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

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

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

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

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

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

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

CAS 原理:

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

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

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 ???? }

?

?


???

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

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

CAS語(yǔ)義上存在的 "
ABA 問(wèn)題"

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

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

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

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

其實(shí), 大部分通過(guò)值的變化來(lái)CAS, 已經(jīng)夠用了.



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

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

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

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

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

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

Java多線(xiàn)程-volatile的原理與技巧


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

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

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

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦?。。?/p>

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 曲阜市| 罗源县| 北流市| 广安市| 泽普县| 唐河县| 柳林县| 商南县| 缙云县| 新建县| 荣成市| 莒南县| 隆德县| 红安县| 六安市| 富蕴县| 莆田市| 武山县| 成武县| 维西| 长乐市| 白水县| 广丰县| 武山县| 和田市| 安多县| 土默特右旗| 聂荣县| 长宁县| 江津市| 泸定县| 茂名市| 沾益县| 仁寿县| 昆明市| 河源市| 项城市| 南陵县| 南阳市| 宜君县| 江陵县|