突擊并發(fā)編程JUC系列演示代碼地址:
https://github.com/mtcarpenter/JavaTutorial
Java
從JDK 1.5
開始提供了java.util.concurrent.atomic
包(以下簡(jiǎn)稱Atomic
包)绘盟,這個(gè)包中的原子操作類提供了一種用法簡(jiǎn)單、性能高效、線程安全地更新一個(gè)變量的方式。原子類通過 CAS (compare and swap)
、 volatile
和native
方法實(shí)現(xiàn)勺拣,比 synchronized
開銷更小,執(zhí)行效率更高,在多線程環(huán)境下搞坝,無鎖的進(jìn)行原子操作。
Atomic包分類
對(duì)其進(jìn)行分類如下:
為了避免一個(gè)章節(jié)內(nèi)容過多魁袜,導(dǎo)致大家提前下車桩撮,會(huì)通過幾個(gè)章節(jié)進(jìn)行 Atomic 包下面的知識(shí)講解敦第。
基本類型
基本類型有AtomicBoolean
、 AtomicInteger
店量、AtomicLong
芜果、這 3 個(gè)類提供的方法幾乎一模一樣,本章節(jié)以 AtomicLong
為案例進(jìn)行講解融师,提前小劇透為了在后面和LongAdder
進(jìn)行對(duì)比右钾,LongAdder
和 AtomicLong
在面試中也被問到過呢。
AtomicLong
的常用方法如下
方法名 | 說明 |
---|---|
long getAndIncrement() |
以原子方式將當(dāng)前值加1旱爆,注意舀射,返回的是舊值。(i++) |
long incrementAndGet() |
以原子方式將當(dāng)前值加1怀伦,注意脆烟,返回的是新值。(++i) |
long getAndDecrement() |
以原子方式將當(dāng)前值減 1空镜,注意浩淘,返回的是舊值 。(i--) |
long decrementAndGet() |
以原子方式將當(dāng)前值減 1吴攒,注意张抄,返回的是舊值 。(--i) |
long addAndGet(int delta) |
以原子方式將輸入的數(shù)值與實(shí)例中的值(AtomicLong 里的value )相加洼怔,并返回結(jié)果 |
long getAndSet(int newValue) |
以原子方式設(shè)置為newValue 的值署惯,并返回舊值 |
long get() |
_獲取 AtomicLong 中的值(value)_
|
boolean compareAndSet(int expect,int update) |
如果輸入的數(shù)值等于預(yù)期值镣隶,則以原子方式將該值設(shè)置為輸入的值极谊。 |
void lazySet(int newValue) |
最終會(huì)設(shè)置成newValue ,使用lazySet 設(shè)置值后安岂,可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)還是可以讀到舊的值轻猖。 |
........ | ......... |
JDK 1.8 新增 |
|
long getAndUpdate(LongUnaryOperator updateFunction) |
定函數(shù)的結(jié)果原子更新當(dāng)前值,返回上一個(gè)值域那。 |
long updateAndGet(LongUnaryOperator updateFunction) |
使用給定函數(shù)的結(jié)果原子更新當(dāng)前值咙边,返回更新的值。 該功能應(yīng)該是無副作用的次员,因?yàn)閲L試的更新由于線程之間的爭(zhēng)用而失敗時(shí)可能會(huì)被重新應(yīng)用败许。 |
........ | ........ |
溫馨提示:
i++
、++i
淑蔚、i--
市殷、--i
只是為了幫助大家理解、理解刹衫、理解醋寝,重要的事情說三遍搞挣,并不是底層的實(shí)現(xiàn)就是它們喲。
小試牛刀
古人云“是騾子是馬拉出來溜溜“甥桂,一段代碼擼起來柿究,走你。
public class AtomicExample1 {
/**
* 初始化為 0
*/
private static AtomicLong count = new AtomicLong(0);
private static LongUnaryOperator longUnaryOperator = new LongUnaryOperator() {
@Override
public long applyAsLong(long operand) {
return 1;
}
};
private static LongBinaryOperator longBinaryOperator = new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
return left + right;
}
};
public static void main(String[] args) {
// 以原子方式將當(dāng)前值加1黄选,返回舊值 (i++): 0
System.out.println("getAndIncrement=" + count.getAndIncrement());
// 以原子方式將當(dāng)前值加1,返回新值(++i) 兩次增加 : 2
System.out.println("incrementAndGet=" + count.incrementAndGet());
//以原子方式將當(dāng)前值減少 1婶肩,返回舊值 (i--):2
System.out.println("incrementAndGet=" + count.getAndDecrement());
//以原子方式將當(dāng)前值減少 1办陷,返回舊值 (--i):0
System.out.println("incrementAndGet=" + count.decrementAndGet());
// 以原子方式將輸入的數(shù)值與實(shí)例中的值(AtomicLong里的value)相加,并返回結(jié)果
System.out.println("addAndGet=" + count.addAndGet(10));
// 以原子方式設(shè)置為`newValue`的值律歼,并返回舊值
System.out.println("getAndSet=" + count.getAndSet(100));
// 獲取 atomicLong 的 value
System.out.println("get=" + count.get());
System.out.println("*********** JDK 1.8 ***********");
// 使用將給定函數(shù)定函數(shù)的結(jié)果原子更新當(dāng)前值民镜,返回上一個(gè)值
// count.get() 為 1:返回 1
System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));
// 返回 applyAsLong 得值
System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));
// 獲取 atomicLong 的 value
System.out.println("get=" + count.get());
// 使用給定函數(shù)應(yīng)用給當(dāng)前值和給定值的結(jié)果原子更新當(dāng)前值,返回上一個(gè)值
// 返回結(jié)果 1险毁,上次結(jié)果
System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
// 返回結(jié)果 3 制圈,上次結(jié)果 1 + 2
System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
// 獲取 atomicLong 的 value
System.out.println("get=" + count.get());
}
}
一串代碼送給你,運(yùn)行結(jié)果請(qǐng)參考:
getAndIncrement=0
incrementAndGet=2
incrementAndGet=2
incrementAndGet=0
addAndGet=10
getAndSet=10
get=100
*********** JDK 1.8 ***********
getAndUpdate=100
getAndUpdate=1
get=1
getAndAccumulate=1
getAndAccumulate=3
get=5
不安全并發(fā)計(jì)數(shù)
public class AtomicExample2 {
// 請(qǐng)求總數(shù)
public static int requestTotal = 1000;
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
long start = System.currentTimeMillis();
for (int i = 0; i < requestTotal; i++) {
new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
add();
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("count=" + count);
System.out.println("耗時(shí):" + (System.currentTimeMillis() - start));
}
private static void add() {
++count;
}
}
懵懂少年是否對(duì) CountDownLatch
有疑問嗎畔况?<br />CountDownLatch
又稱 倒計(jì)數(shù)器
, 也就是讓一個(gè)線程或者多個(gè)線程等待其他線程結(jié)束后再繼續(xù)自己的操作鲸鹦,類似加強(qiáng)版 join()
。
-
countDown
: 執(zhí)行一次跷跪, 計(jì)數(shù)器的數(shù)值 -1馋嗜。 -
await
:等待計(jì)算器的值為 0,才進(jìn)行后面的操作吵瞻,就像一個(gè)柵欄一樣葛菇。
AtomicLong 實(shí)現(xiàn)并發(fā)計(jì)數(shù)
public class AtomicExample3 {
// 請(qǐng)求總數(shù)
public static int requestTotal = 5000;
public static AtomicLong count = new AtomicLong(0);
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
long start = System.currentTimeMillis();
for (int i = 0; i < requestTotal; i++) {
new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
add();
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("count=" + count.get());
System.out.println("耗時(shí):" + (System.currentTimeMillis() - start));
count.addAndGet(200);
System.out.println("count=" + count.get());
}
private static void add() {
//count.incrementAndGet();
count.getAndIncrement();
}
}
走進(jìn)源碼
一段段小小的案例演示,已經(jīng)無法滿足懵懂少年了橡羞,那就加餐眯停,加餐,下面分類介紹下面 JDk 1.7
和 JDK1.8
的底層實(shí)現(xiàn)卿泽。
在 Jdk1.7 中莺债,AtomicLong 的關(guān)鍵代碼如下:
static {
try {
// 獲取內(nèi)存 value 內(nèi)存中的地址
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 省略其他代碼.....
public final long getAndIncrement() {
for(;;)
long current = get();
long next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
getAndIncrement()
進(jìn)去乍一看,無限循環(huán)又厉,這不就如一個(gè)癡情男孩一樣九府,一直等待他的女神回信,不回信一直等啊等覆致。
-
long current = get();
獲取AtomicLong
中的 value 值侄旬。<br /> -
long next = current + 1;
: 在當(dāng)前記錄 + 1。 -
compareAndSet(current, next)
: 通過 compareAndSet方法來進(jìn)行原子更新操作煌妈,將當(dāng)前的值跟內(nèi)存中的值進(jìn)行比較儡羔,相等宣羊,則內(nèi)存中沒有被修改,直接寫入新的值到主內(nèi)存中汰蜘,并return true仇冯,否則直接return false。
在 Jdk1.8 中族操,AtomicLong 的關(guān)鍵代碼如下:
/**
* 原子更新導(dǎo)致值
*
* @return 返回舊值
*/
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
//
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
-
var1
: 需要修改的類對(duì)象<br /> -
var2
:修改的字段的內(nèi)存地址<br /> -
var6
是修改前字段的值苛坚,若是沒其余線程修改即與var2
相等<br /> -
var6+var4
: 修改后字段的值,也就是新值<br /> -
compareAndSwapLong
:當(dāng)字段實(shí)際值和var6值相當(dāng)?shù)臅r(shí)候色难,才會(huì)設(shè)置其為 var6+var4 泼舱。 -
this.getLongVolatile(var1, var2)
:獲取對(duì)象obj中偏移量為offset的變量對(duì)應(yīng) volatile 語義的值。
從上面的代碼可以看出AtomicLong
在 jdk 7 的循環(huán)邏輯枷莉,在 JDK 8 中原子操作類 unsafe 內(nèi)置了娇昙。之所以內(nèi)置應(yīng)該是考慮到這個(gè)函數(shù)在其他地方也會(huì)用到,而內(nèi)置可以提高復(fù)用性笤妙。<br />
歡迎關(guān)注公眾號(hào) 山間木匠 冒掌, 我是小春哥,從事 Java 后端開發(fā)蹲盘,會(huì)一點(diǎn)前端股毫、通過持續(xù)輸出系列技術(shù)文章與文會(huì)友,如果本文能為您提供幫助辜限,歡迎大家關(guān)注皇拣、 點(diǎn)贊、分享支持薄嫡,我們下期再見氧急!