java自旋鎖

概念
一種鎖轨蛤,與互斥鎖相似蜜宪,基本作用是用于線程(進(jìn)程)之間的同步。與普通鎖不同的是祥山,一個(gè)線程A在獲得普通鎖后圃验,如果再有線程B試圖獲取鎖,那么這個(gè)線程B將會(huì)掛起(阻塞)缝呕;試想下澳窑,如果兩個(gè)線程資源競爭不是特別激烈,而處理器阻塞一個(gè)線程引起的線程上下文的切換的代價(jià)高于等待資源的代價(jià)的時(shí)候(鎖的已保持者保持鎖時(shí)間比較短)供常,那么線程B可以不放棄CPU時(shí)間片照捡,而是在“原地”忙等,直到鎖的持有者釋放了該鎖话侧,這就是自旋鎖的原理栗精,可見自旋鎖是一種非阻塞鎖。
自旋鎖可能引起的問題
1.過多占據(jù)CPU時(shí)間:如果鎖的當(dāng)前持有者長時(shí)間不釋放該鎖瞻鹏,那么等待者將長時(shí)間的占據(jù)cpu時(shí)間片悲立,導(dǎo)致CPU資源的浪費(fèi),因此可以設(shè)定一個(gè)時(shí)間新博,當(dāng)鎖持有者超過這個(gè)時(shí)間不釋放鎖時(shí)薪夕,等待者會(huì)放棄CPU時(shí)間片阻塞;
2.死鎖問題:試想一下赫悄,有一個(gè)線程連續(xù)兩次試圖獲得自旋鎖(比如在遞歸程序中)原献,第一次這個(gè)線程獲得了該鎖,當(dāng)?shù)诙卧噲D加鎖的時(shí)候埂淮,檢測到鎖已被占用(其實(shí)是被自己占用)姑隅,那么這時(shí),線程會(huì)一直等待自己釋放該鎖倔撞,而不能繼續(xù)執(zhí)行讲仰,這樣就引起了死鎖。因此遞歸程序使用自旋鎖應(yīng)該遵循以下原則:遞歸程序決不能在持有自旋鎖時(shí)調(diào)用它自己痪蝇,也決不能在遞歸調(diào)用時(shí)試圖獲得相同的自旋鎖鄙陡。
3。aba問題:java中自旋鎖一般是利用CAS(compare And set)操作實(shí)現(xiàn)躏啰。

我們先來看一個(gè)多線程的運(yùn)行場景:
時(shí)間點(diǎn)1 :線程1查詢值是否為A
時(shí)間點(diǎn)2 :線程2查詢值是否為A
時(shí)間點(diǎn)3 :線程2比較并更新值為B
時(shí)間點(diǎn)4 :線程2查詢值是否為B
時(shí)間點(diǎn)5 :線程2比較并更新值為A
時(shí)間點(diǎn)6 :線程1比較并更新值為C
在這個(gè)線程執(zhí)行場景中趁矾,2個(gè)線程交替執(zhí)行。線程1在時(shí)間點(diǎn)6的時(shí)候依然能夠正常的進(jìn)行CAS操作给僵,盡管在時(shí)間點(diǎn)2到時(shí)間點(diǎn)6期間已經(jīng)發(fā)生一些意想不到的變化毫捣, 但是線程1對(duì)這些變化卻一無所知,因?yàn)閷?duì)線程1來說A的確還在。通常將這類現(xiàn)象稱為ABA問題培漏。ABA發(fā)生了溪厘,但線程不知道。又或者鏈表的頭在變化了兩次后恢復(fù)了原值牌柄,但是不代表鏈表就沒有變化畸悬。
**ABA問題隱患 **
獲取上面的描述ABA問題帶來的隱患沒有直觀的認(rèn)識(shí),那我們來看下維基百科上面的形象描述:
你拿著一個(gè)裝滿錢的手提箱在飛機(jī)場珊佣,此時(shí)過來了一個(gè)火辣性感的美女蹋宦,然后她很暖昧地挑逗著你,并趁你不注意的時(shí)候咒锻,把用一個(gè)一模一樣的手提箱和你那裝滿錢的箱子調(diào)了個(gè)包冷冗,然后就離開了,你看到你的手提箱還在那惑艇,于是就提著手提箱去趕飛機(jī)去了蒿辙。

4.自旋鎖實(shí)現(xiàn)的原理
在java1.5版本及以上的并發(fā)框架java.util.concurrent 的atmoic包下的類基本都是自旋鎖的實(shí)現(xiàn),由于原理是CAS滨巴,所以是非阻塞的框架思灌。

atmoic包下的類

先看第一個(gè)類AtomicBoolean

 private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
      try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicBoolean.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

這里是先實(shí)例化了Unsafe 這個(gè)類,然后得到了value屬性的內(nèi)存的虛擬地址valueOffset恭取。Unsafe 這個(gè)類是java調(diào)用底層c的一些接口泰偿,由于java是安全的語言,所以這個(gè)類并沒有文檔蜈垮,也不建議程序員使用耗跛,但是這個(gè)類非常有用,好多開源的底層框架都是基于他實(shí)現(xiàn)的(Netty攒发、Hazelcast调塌、Cassandra、Mockito / EasyMock / JMock / PowerMock晨继、Scala Specs烟阐、Spock、Robolectric紊扬、Grails、Neo4j唉擂、Spring Framework餐屎、Akka、Apache Kafka玩祟、Apache Wink腹缩、Apache Storm、Apache Hadoop、Apache Continuum)藏鹊。這里的objectFieldOffset方法

/**
 * Gets the raw byte offset from the start of an object's memory to
 * the memory used to store the indicated instance field.
 * @param field non-null; the field in question, which must be an
 * instance field
 * @return the offset to the field
 */

 public long objectFieldOffset(Field field) {

     if (Modifier.isStatic(field.getModifiers())) {

     throw new IllegalArgumentException(

     "valid for instance fields only");
 
     }
     return objectFieldOffset0(field);

 }

得到了內(nèi)存虛擬地址(非物理地址)润讥,這里寫了個(gè)測試類,
在使用Unsafe之前盘寡,我們需要?jiǎng)?chuàng)建Unsafe對(duì)象的實(shí)例楚殿。這并不像Unsafe unsafe = new Unsafe()
這么簡單,因?yàn)閁nsafe的
構(gòu)造器是私有的竿痰。它也有一個(gè)靜態(tài)的getUnsafe()
方法脆粥,但如果你直接調(diào)用Unsafe.getUnsafe()
,你可能會(huì)得到SecurityException異常影涉。只能從受信任的代碼中使用這個(gè)方法变隔。
public static Unsafe getUnsafe() {
Class cc = sun.reflect.Reflection.getCallerClass(2);
if (cc.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}

這就是Java如何驗(yàn)證代碼是否可信。它只檢查我們的代碼是否由主要的類加載器加載蟹倾。
我們可以令我們的代碼“受信任”匣缘。運(yùn)行程序時(shí),使用bootclasspath 選項(xiàng)鲜棠,指定系統(tǒng)類路徑加上你使用的一個(gè)Unsafe路徑肌厨。
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar: . com.alibaba.otter.canal.common.ObjectLocationTest
但這麻煩和困難。
Unsafe類包含一個(gè)私有的岔留、名為theUnsafe的實(shí)例夏哭,我們可以通過Java反射竊取該變量。

theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeInstance.setAccessible(true);  
unsafe  = (Unsafe)  theUnsafeInstance.get(Unsafe.class);  

完整測試類

package com.alibaba.otter.canal.common;
import java.lang.reflect.Field;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import sun.misc.Unsafe;
@SuppressWarnings("restriction")
public class ObjectLocationTest extends AbstractZkTest {
    @SuppressWarnings("unused")
    private static int apple = 10;  
    @SuppressWarnings("unused")
    private int orange = 10;  
    Unsafe unsafe  = null;
    @Before
    public void setUp() {
        Field theUnsafeInstance;
        try {
            theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafeInstance.setAccessible(true);  
            unsafe  = (Unsafe)  theUnsafeInstance.get(Unsafe.class);  
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }  
        
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testUnsafe() {
        try {
            Field appleField = ObjectLocationTest.class.getDeclaredField("apple");
            System.out.println("Location of Apple: " + unsafe.staticFieldOffset(appleField));  
              
            Field orangeField = ObjectLocationTest.class.getDeclaredField("orange");  
            System.out.println("Location of Orange: " + unsafe.objectFieldOffset(orangeField));
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
         
    }
}

直接運(yùn)行junit方式即可献联。

由于value是通過volatile關(guān)鍵字修飾的竖配,我們知道volatile是能保證原子性操作的并發(fā)性的。所以在AtomicBoolean的源碼中g(shù)et set方法都是直接return的里逆,但是getAndSet方法由于是非原子性的 這里用了compareAndSet方法 此方法中調(diào)用的正是unsafe.compareAndSwapInt的方法

public final boolean getAndSet(boolean newValue) {
        for (;;) {
            boolean current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }
public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }
/**
 * Performs a compare-and-set operation on an <code>int</code>
 * field within the given object.
 * @param obj non-null; object containing the field
 * @param offset offset to the field within <code>obj</code>
 * @param expectedValue expected value of the field
 * @param newValue new value to store in the field if the contents are
 * as expected
 * @return <code>true</code> if the new value was in fact stored, and
 * <code>false</code> if not
 */
 public native boolean compareAndSwapInt(Object obj, long offset,

 int expectedValue, int newValue);

還有weakCompareAndSet进胯、lazySet 方法也是通過unsafe的unsafe.compareAndSwapInt和unsafe.putOrderedInt實(shí)現(xiàn)的

在看AtomicInteger類其實(shí)和AtomicBoolean基本一致 只不過增加了一些方法AtomicReference、AtomicLong這幾個(gè)應(yīng)該是屬于一類的 都結(jié)合了volatile關(guān)鍵字原押。
AtomicIntegerArray胁镐、AtomicLongArray 、AtomicReferenceArray 都是原子更新引用類型數(shù)組里的元素诸衔。
AtomicReferenceFieldUpdater盯漂、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 可以對(duì)volatie屬性進(jìn)行原子更新笨农,利用的是反射就缆。
AtomicMarkableReference 和AtomicStampedReference 是為了解決上面說的ABA問題提供的類
AtomicMarkableReference相當(dāng)于一個(gè)[引用,integer]的二元組,AtomicStampedReference 相當(dāng)于一個(gè)[引用,boolean]的二元組谒亦。
AtomicStampedReference可用來作為帶版本號(hào)的原子引用竭宰,而AtomicMarkableReference可用于表示如:已刪除的節(jié)點(diǎn)空郊。

最后附上簡單的java自旋鎖實(shí)現(xiàn)

public class SpinLock {

  private AtomicReference<Thread> sign =new AtomicReference<>();

  public void lock(){
    Thread current = Thread.currentThread();
    while(!sign .compareAndSet(null, current)){
    }
  }

  public void unlock (){
    Thread current = Thread.currentThread();
    sign .compareAndSet(current, null);
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市切揭,隨后出現(xiàn)的幾起案子狞甚,更是在濱河造成了極大的恐慌,老刑警劉巖廓旬,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哼审,死亡現(xiàn)場離奇詭異,居然都是意外死亡嗤谚,警方通過查閱死者的電腦和手機(jī)棺蛛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巩步,“玉大人旁赊,你說我怎么就攤上這事∫我埃” “怎么了终畅?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長竟闪。 經(jīng)常有香客問我离福,道長,這世上最難降的妖魔是什么炼蛤? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任妖爷,我火速辦了婚禮,結(jié)果婚禮上理朋,老公的妹妹穿的比我還像新娘絮识。我一直安慰自己,他們只是感情好嗽上,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布次舌。 她就那樣靜靜地躺著,像睡著了一般兽愤。 火紅的嫁衣襯著肌膚如雪彼念。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天浅萧,我揣著相機(jī)與錄音逐沙,去河邊找鬼。 笑死洼畅,一個(gè)胖子當(dāng)著我的面吹牛酱吝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播土思,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼务热,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了己儒?” 一聲冷哼從身側(cè)響起崎岂,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闪湾,沒想到半個(gè)月后冲甘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡途样,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年江醇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片何暇。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陶夜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出裆站,到底是詐尸還是另有隱情条辟,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布宏胯,位于F島的核電站羽嫡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏肩袍。R本人自食惡果不足惜杭棵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氛赐。 院中可真熱鬧魂爪,春花似錦、人聲如沸鹰祸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛙婴。三九已至粗井,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間街图,已是汗流浹背浇衬。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留餐济,地道東北人耘擂。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像絮姆,于是被迫代替她去往敵國和親醉冤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秩霍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容