Java語(yǔ)言中cas指令的無鎖編程實(shí)現(xiàn)實(shí)例
最開始接觸到相關(guān)的內(nèi)容應(yīng)該是從volatile關(guān)鍵字開始的吧,知道它可以保證變量的可見性,而且利用它可以實(shí)現(xiàn)讀與寫的原子操作。。。但是要實(shí)現(xiàn)一些復(fù)合的操作volatile就無能為力了。。。最典型的代表是遞增和遞減的操作。。。。
我們知道,在并發(fā)的環(huán)境下,要實(shí)現(xiàn)數(shù)據(jù)的一致性,最簡(jiǎn)單的方式就是加鎖,保證同一時(shí)刻只有一個(gè)線程可以對(duì)數(shù)據(jù)進(jìn)行操作。。。。例如一個(gè)計(jì)數(shù)器,我們可以用如下的方式來實(shí)現(xiàn):
public class Counter {
private volatile int a = 0;
public synchronized int incrAndGet(int number) {
this.a += number;
return a;
}
public synchronized int get() {
return a;
}
}
我們對(duì)操作都用synchronized關(guān)鍵字進(jìn)行修飾,保證對(duì)屬性a的同步訪問。。。這樣子確實(shí)可以保證在并發(fā)環(huán)境下a的一致性,但是由于使用了鎖,鎖的開銷,線程的調(diào)度等等會(huì)使得程序的伸縮性受到了限制,于是就有了很多無鎖的實(shí)現(xiàn)方式。。。。
其實(shí)這些無鎖的方法都利用了處理器所提供的一些CAS(compare and switch)指令,這個(gè)CAS到底干了啥事情呢,可以用下面這個(gè)方法來說明CAS所代表的語(yǔ)義:
public synchronized int compareAndSwap(int expect, int newValue) {
int old = this.a;
if (old == expect) {
this.a = newValue;
}
return old;
}
好吧,通過代碼應(yīng)該對(duì)CAS語(yǔ)義的標(biāo)書很清楚了吧,好像現(xiàn)在大多數(shù)的處理器都實(shí)現(xiàn)了原子的CAS指令了吧。。
好啦,那么接下來來看看在java中CAS都用在了什么地方了吧,首先來看AtomicInteger類型吧,這個(gè)是并發(fā)庫(kù)里面提供的一個(gè)類型:
private volatile int value;
這個(gè)是內(nèi)部定義的一個(gè)屬性吧,用于保存值,由于是volatile類型的,所以可以保證線程之間的可見性以及讀寫的原子性。。。
那么接下來來看看幾個(gè)比較常用的方法:
public final int addAndGet(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return next;
}
}
這個(gè)方法的作用是在當(dāng)前值的基礎(chǔ)上加上delta,這里可以看到整個(gè)方法中并沒有加鎖,這代碼其實(shí)就算是java中實(shí)現(xiàn)無鎖計(jì)數(shù)器的方法,這里compareAndSet方法的定義如下:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
由于調(diào)用了unsafe的方法,所以這個(gè)就無能為力了,其實(shí)應(yīng)該能猜到JVM調(diào)用了處理器本身的CAS指令來實(shí)現(xiàn)原子的操作。。。
基本上AtomicInteger類型的重要方法都是采用無鎖的方式實(shí)現(xiàn)的。。因此在并發(fā)環(huán)境下,用這種類型能有更好的性能。。。
上面算是搞定了在java中實(shí)現(xiàn)無鎖的計(jì)數(shù)器,接下來來看看如何實(shí)現(xiàn)無鎖棧,直接貼代碼了,代碼是從《JAVA并發(fā)編程實(shí)戰(zhàn)》中模仿下來的:
package concurrenttest;
import java.util.concurrent.atomic.AtomicReference;
public class ConcurrentStack<e> {
AtomicReference<node<e>> top = new AtomicReference<node<e>>();
public void push(E item) {
Node<e> newHead = new Node<e>(item);
Node<e> oldHead;
while (true) {
oldHead = top.get();
newHead.next = oldHead;
if (top.compareAndSet(oldHead, newHead)) {
return;
}
}
}
public E pop() {
while (true) {
Node<e> oldHead = top.get();
if (oldHead == null) {
return null;
}
Node<e> newHead = oldHead.next;
if (top.compareAndSet(oldHead, newHead)) {
return oldHead.item;
}
}
}
private static class Node<e> {
public final E item;
public Node<e> next;
public Node(E item) {
this.item = item;
}
}
}
好啦,上面的代碼就算是實(shí)現(xiàn)了一個(gè)無鎖的棧,簡(jiǎn)單吧。。。在并發(fā)環(huán)境中,無鎖的數(shù)據(jù)結(jié)構(gòu)伸縮性能夠比用鎖好得多。。。
在提到無鎖編程的時(shí)候,就不得不提到無鎖隊(duì)列,其實(shí)在concurrent庫(kù)中已經(jīng)提供了無鎖隊(duì)列的實(shí)現(xiàn):ConcurrentLinkedQueue,我們來看看它的重要的方法實(shí)現(xiàn)吧:
public boolean offer(E e) {
checkNotNull(e);
final Node<e> newNode = new Node<e>(e);
for (Node<e> t = tail, p = t;;) {
Node<e> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
這個(gè)方法用于在隊(duì)列的尾部添加元素,這里可以看到?jīng)]有加鎖,對(duì)于具體的無鎖算法,采用的是Michael-Scott提出的非阻塞鏈表鏈接算法。。。具體是怎么樣子的,可以到《JAVA并發(fā)編程實(shí)戰(zhàn)》中去看吧,有比較詳細(xì)的介紹。
另外對(duì)于其他方法,其實(shí)都是采用無鎖的方式實(shí)現(xiàn)的。
最后,在實(shí)際的編程中,在并發(fā)環(huán)境中最好還是采用這些無鎖的實(shí)現(xiàn),畢竟它的伸縮性更好。
總結(jié)
以上是本文關(guān)于Java語(yǔ)言中cas指令的無鎖編程實(shí)現(xiàn)實(shí)例的全部介紹,希望對(duì)大家有所幫助!
相關(guān)文章
java實(shí)現(xiàn)手寫一個(gè)簡(jiǎn)單版的線程池
有些人可能對(duì)線程池比較陌生,并且更不熟悉線程池的工作原理。本文就來手寫一個(gè)簡(jiǎn)單版的線程池,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
IntelliJ IDEA打開多個(gè)Maven的module且相互調(diào)用代碼的方法
這篇文章主要介紹了IntelliJ IDEA打開多個(gè)Maven的module且相互調(diào)用代碼的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-02-02
Spring Boot 2.x基礎(chǔ)教程之配置元數(shù)據(jù)的應(yīng)用
這篇文章主要介紹了Spring Boot 2.x基礎(chǔ)教程之配置元數(shù)據(jù)的應(yīng)用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
SpringBoot整合Xxl-job實(shí)現(xiàn)定時(shí)任務(wù)的全過程
XXL-JOB是一個(gè)分布式任務(wù)調(diào)度平臺(tái),其核心設(shè)計(jì)目標(biāo)是開發(fā)迅速、學(xué)習(xí)簡(jiǎn)單、輕量級(jí)、易擴(kuò)展,下面這篇文章主要給大家介紹了關(guān)于SpringBoot整合Xxl-job實(shí)現(xiàn)定時(shí)任務(wù)的相關(guān)資料,需要的朋友可以參考下2022-01-01
Logback 使用TurboFilter實(shí)現(xiàn)日志級(jí)別等內(nèi)容的動(dòng)態(tài)修改操作
這篇文章主要介紹了Logback 使用TurboFilter實(shí)現(xiàn)日志級(jí)別等內(nèi)容的動(dòng)態(tài)修改操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
基于list stream: reduce的使用實(shí)例
這篇文章主要介紹了list stream: reduce的使用實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
Mybatis調(diào)用視圖和存儲(chǔ)過程的方法
MyBatis支持普通sql的查詢、視圖的查詢、存儲(chǔ)過程調(diào)用,是一種非常優(yōu)秀的持久層框架。接下來通過本文給大家介紹mybatis調(diào)用視圖和存儲(chǔ)過程的方法,感興趣的朋友一起看看吧2016-10-10
java實(shí)現(xiàn)連接mysql數(shù)據(jù)庫(kù)單元測(cè)試查詢數(shù)據(jù)的實(shí)例代碼
下面小編就為大家?guī)硪黄猨ava實(shí)現(xiàn)連接mysql數(shù)據(jù)庫(kù)單元測(cè)試查詢數(shù)據(jù)的實(shí)例代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10

