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