java intbyreference_java并发包(1)-AtomicReference和AtomicStampedReference
AtomicReference原子應(yīng)用類,可以保證你在修改對象引用時的線程安全性,比較時可以按照偏移量進(jìn)行
這里的cas操作本身是原子的,但是在某些場景下會出現(xiàn)異常場景
線程判斷被修改對象是否可以正確寫入的條件是對象的當(dāng)前值和期望是否一致。這個邏輯從一般意義上來說是正確的。但有可能出現(xiàn)一個小小的例外,就是當(dāng)你獲得對象當(dāng)前數(shù)據(jù)后,在準(zhǔn)備修改為新值前,對象的值被其他線程連續(xù)修改了2次,而經(jīng)過這2次修改后,對象的值又恢復(fù)為舊值。這樣,當(dāng)前線程就無法正確判斷這個對象究竟是否被修改過。如圖所示,顯示了這種情況。(圖片是轉(zhuǎn)載而來)
一般來說,發(fā)生這種情況的概率很小。而且即使發(fā)生了,可能也不是什么大問題。比如,我們只是簡單得要做一個數(shù)值加法,即使在我取得期望值后,這個數(shù)字被不斷的修改,只要它最終改回了我的期望值,我的加法計算就不會出錯。也就是說,當(dāng)你修改的對象沒有過程的狀態(tài)信息,所有的信息都只保存于對象的數(shù)值本身。
但是,在現(xiàn)實(shí)中,還可能存在另外一種場景。就是我們是否能修改對象的值,不僅取決于當(dāng)前值,還和對象的過程變化有關(guān),這時,AtomicReference就無能為力了。
打一個比方,如果有一家蛋糕店,為了挽留客戶,絕對為貴賓卡里余額小于20元的客戶一次性贈送20元,刺激消費(fèi)者充值和消費(fèi)。但條件是,每一位客戶只能被贈送一次。
現(xiàn)在,我們就來模擬這個場景,為了演示AtomicReference,我在這里使用AtomicReference實(shí)現(xiàn)這個功能。
package algorithmProject.concurrent;
import java.util.concurrent.atomic.AtomicReference;
/**
* Created by wangkai on 2017/4/24.
*/
public class AtomicReferenceDemo {
// 設(shè)置賬戶初始值小于20,顯然這是一個需要被充值的賬戶
static AtomicReference money = new AtomicReference(19);
public static void main(String args[]) {
//模擬多個線程同時更新后臺數(shù)據(jù)庫,為用戶充值
for (int i = 0; i < 3; i++) {
new Thread() {
public void run() {
while (true) {
while (true) {
Integer m = money.get();
if (m < 20) {
if (money.compareAndSet(m, m + 20)) {
System.out.println("余額小于20元,充值成功,余額:" + money.get() + "元");
break;
}
} else {
//System.out.println("余額大于20元,無需充值");
break;
}
}
}
}
}.start();
}
//有一個線程一直在消費(fèi)
new Thread() {
public void run() {
for (int i = 0; i < 100; i++) {
while (true) {
Integer m = money.get();
if (m > 10) {
System.out.println("大于10元");
if (money.compareAndSet(m, m - 10)) {
System.out.println("成功消費(fèi)10元,余額:" + money.get());
break;
}
} else {
System.out.println("沒有足夠的金額");
break;
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}.start();
}
}
首先判斷用戶余額并給予贈予金額。如果已經(jīng)被其他用戶處理,那么當(dāng)前線程就會失敗。因此,可以確保用戶只會被充值一次。
此時,如果很不幸的,用戶正好正在進(jìn)行消費(fèi),就在贈予金額到賬的同時,他進(jìn)行了一次消費(fèi),使得總金額又小于20元,并且正好累計消費(fèi)了20元。使得消費(fèi)、贈予后的金額等于消費(fèi)前、贈予前的金額。這時,后臺的贈予進(jìn)程就會誤以為這個賬戶還沒有贈予,所以,存在被多次贈予的可能。萬幸的是jdk給我提供了一個類AtomicStampedReference
AtomicReference無法解決上述問題的根本是因為對象在修改過程中,丟失了狀態(tài)信息。對象值本身與狀態(tài)被畫上了等號。因此,我們只要能夠記錄對象在修改過程中的狀態(tài)值,就可以很好的解決對象被反復(fù)修改導(dǎo)致線程無法正確判斷對象狀態(tài)的問題。
AtomicStampedReference正是這么做的。它內(nèi)部不僅維護(hù)了對象值,還維護(hù)了一個時間戳(我這里把它稱為時間戳,實(shí)際上它可以使任何一個整數(shù),它使用整數(shù)來表示狀態(tài)值)。當(dāng)AtomicStampedReference對應(yīng)的數(shù)值被修改時,除了更新數(shù)據(jù)本身外,還必須要更新時間戳。當(dāng)AtomicStampedReference設(shè)置對象值時,對象值以及時間戳都必須滿足期望值,寫入才會成功。因此,即使對象值被反復(fù)讀寫,寫回原值,只要時間戳發(fā)生變化,就能防止不恰當(dāng)?shù)膶懭搿?/p>
package algorithmProject.concurrent;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* Created by wangkai on 2017/4/24.
*/
public class AtomicStampedReferenceDemo {
static AtomicStampedReference money = new AtomicStampedReference(19, 0);
public static void main(String[] args) {
//模擬多個線程同時更新后臺數(shù)據(jù)庫,為用戶充值
for (int i = 0; i < 3; i++) {
//獲得當(dāng)前時間戳
final int timestamp = money.getStamp();
new Thread() {
public void run () {
while (true) {
while (true) {
//獲得當(dāng)前對象引用
Integer m = money.getReference();
if (m < 20) {
//比較設(shè)置?參數(shù)依次為:期望值?寫入新值?期望時間戳?新時間戳
if (money.compareAndSet(m, m + 20, timestamp, timestamp + 1)) {
System.out.println("余額小于20元,充值成功,余額:" + money.getReference() + "元");
break;
}
} else {
//System.out.println("余額大于20元,無需充值");
break;
}
}
}
}
}.start();
}
//用戶消費(fèi)線程,模擬消費(fèi)行為
new Thread() {
public void run() {
for (int i = 0; i < 100; i++) {
while (true) {
int timestamp = money.getStamp();
Integer m = money.getReference();
if (m > 10) {
System.out.println("大于10元");
if (money.compareAndSet(m, m - 10, timestamp, timestamp + 1)) {
System.out.println("成功消費(fèi)10元,余額:" + money.getReference());
break;
}
} else {
System.out.println("沒有足夠的金額");
break;
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}.start();
}
}
總結(jié)
以上是生活随笔為你收集整理的java intbyreference_java并发包(1)-AtomicReference和AtomicStampedReference的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java io字符输出流_Java字符
- 下一篇: mysql索引 物理文件_MySQL架构