java自旋鎖

概念
一種鎖,與互斥鎖相似,基本作用是用于線程(進(jìn)程)之間的同步。與普通鎖不同的是,一個(gè)線程A在獲得普通鎖后,如果再有線程B試圖獲取鎖,那么這個(gè)線程B將會(huì)掛起(阻塞);試想下,如果兩個(gè)線程資源競(jìng)爭(zhēng)不是特別激烈,而處理器阻塞一個(gè)線程引起的線程上下文的切換的代價(jià)高于等待資源的代價(jià)的時(shí)候(鎖的已保持者保持鎖時(shí)間比較短),那么線程B可以不放棄CPU時(shí)間片,而是在“原地”忙等,直到鎖的持有者釋放了該鎖,這就是自旋鎖的原理,可見自旋鎖是一種非阻塞鎖。
自旋鎖可能引起的問題
1.過多占據(jù)CPU時(shí)間:如果鎖的當(dāng)前持有者長(zhǎng)時(shí)間不釋放該鎖,那么等待者將長(zhǎng)時(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í)候,檢測(cè)到鎖已被占用(其實(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)行場(chǎng)景:
時(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í)行場(chǎng)景中,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ī)場(chǎng),此時(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è)測(cè)試類,
在使用Unsafe之前,我們需要?jiǎng)?chuàng)建Unsafe對(duì)象的實(shí)例。這并不像Unsafe unsafe = new Unsafe()
這么簡(jiǎn)單,因?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);  

完整測(cè)試類

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)。

最后附上簡(jiǎ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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容