概念
一種鎖轨蛤,與互斥鎖相似蜜宪,基本作用是用于線程(進(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滨巴,所以是非阻塞的框架思灌。
先看第一個(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);
}
}