一、原子操作
Java中可以通過鎖和循環(huán)CAS的方式來實現(xiàn)原子操作型凳。JVM中的CAS操作正是利用了上文中提到的處理器提供的CMPXCHG指令實現(xiàn)的。自旋CAS實現(xiàn)的基本思路就是循環(huán)進行CAS操作直到成功為止腮恩,具體的類可以參見juc下的atomic包內(nèi)的原子類峰尝。
原子操作是針對CPU來說的,是一個不可能再分割的一個操作衬吆,要么不執(zhí)行梁钾,要么執(zhí)行完畢。CAS的鎖就是操作系統(tǒng)的緩存行鎖或者總線鎖逊抡。
多個CPU對主內(nèi)存同一塊緩存行進行CAS操作時姆泻,會遵循操作系統(tǒng)的緩存行一致性協(xié)議MESI,基于處理器提供的CMPXCHG指令冒嫡,CPU會把變量讀取到CPU寄存器中進行比較與交換操作(也就是CAS操作)拇勃,優(yōu)先操作完的CPU會把緩存行鎖住并修改緩存行為修改M狀態(tài),其它CPU變?yōu)闊o效I孝凌。操作失敗的CPU會等待操作成功的CPU把值寫回主存內(nèi)方咆,這也是為什么CAS要通過循環(huán)來實現(xiàn)原子操作。
注意:一個原子操作在Java中是通過CAS操作實現(xiàn)蟀架,那么一系列的原子操作是怎么實現(xiàn)的瓣赂?通過同步塊加鎖就可以實現(xiàn)。
二片拍、Atomic
在Atomic包里一共有12個類煌集,四種原子更新方式,分別是原子更新基本類型捌省,原子更新數(shù)組牙勘,原子更新引用和原子更新字段。Atomic包里的類基本都是使用Unsafe實現(xiàn)的包裝類所禀。
- 基本類:AtomicInteger方面、AtomicLong、AtomicBoolean色徘;
- 引用類型:AtomicReference恭金、AtomicReference的ABA實例、AtomicStampedRerence褂策、AtomicMarkableReference横腿;
- 數(shù)組類型:AtomicIntegerArray颓屑、AtomicLongArray、AtomicReferenceArray耿焊;
- 屬性原子修改器:AtomicIntegerFieldUpdater揪惦、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
應(yīng)用
創(chuàng)建相應(yīng)的操作類型罗侯,傳入初始值器腋。
public static void main(String[] args) throws InterruptedException {
AtomicInteger atomicInteger = new AtomicInteger(1);
for(int i = 0; i<100; i++)
{
new Thread(new Runnable() {
@Override
public void run() {
atomicInteger.incrementAndGet();
}
}).start();
}
Thread.sleep(1000);
System.out.println("原子操作結(jié)果---->"+atomicInteger.get());
}
原子操作ABA問題
多線程同時操作同一個AtomicInteger時,比如T1 T2同時操作AtomicInteger atomic時钩杰,atomic初始值為2纫塌,T1 先讀取atomic,值為2讲弄,這是T2進來措左,多次修改了atomic,先 2-->1避除,再 1-->2怎披,T2結(jié)束,T1繼續(xù)往下瓶摆,把atomic修改了凉逛,而且修改成功了。這就是ABA問題赏壹,問題主要出現(xiàn)在T2先執(zhí)行,期間T1修改了atomic而T2卻不知道衔沼。
JUC包中AtomicReference可以解決AtomicInteger ABA的問題蝌借,原理就是引進了版本這一概念,atomic每修改一次指蚁,版本就變一次菩佑。這樣,T1修改了atomic后T2能感知的到凝化。
private static AtomicStampedReference<Integer> atomicStampedRef =
new AtomicStampedReference<>(1, 0);
public static void main(String[] args){
Thread main = new Thread(() -> {
int stamp = atomicStampedRef.getStamp(); //獲取當前標識別
System.out.println("操作線程" + Thread.currentThread()+ "stamp="+stamp + ",初始值 a = " + atomicStampedRef.getReference());
try {
Thread.sleep(1000); //等待1秒 稍坯,以便讓干擾線程執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1); //此時expectedReference未發(fā)生改變,但是stamp已經(jīng)被修改了,所以CAS失敗
System.out.println("操作線程" + Thread.currentThread() + "stamp="+stamp + ",CAS操作結(jié)果: " + isCASSuccess);
},"主操作線程");
Thread other = new Thread(() -> {
int stamp = atomicStampedRef.getStamp();
atomicStampedRef.compareAndSet(1,2,stamp,stamp+1);
System.out.println("操作線程" + Thread.currentThread() + "stamp="+atomicStampedRef.getStamp() +",【increment】 ,值 = "+ atomicStampedRef.getReference());
stamp = atomicStampedRef.getStamp();
atomicStampedRef.compareAndSet(2,1,stamp,stamp+1);
System.out.println("操作線程" + Thread.currentThread() + "stamp="+atomicStampedRef.getStamp() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());
},"干擾線程");
main.start();
other.start();
}
三搓劫、Unsafe魔術(shù)類
Unsafe是位于sun.misc包下的一個類瞧哟,主要提供一些用于執(zhí)行低級別、不安全操作的方法枪向,如直接訪問系統(tǒng)內(nèi)存資源勤揩、自主管理內(nèi)存資源等,這些方法在提升Java運行效率秘蛔、增強Java語言底層資源操作能力方面起到了很大的作用陨亡。但由于Unsafe類使Java語言擁有了類似C語言指針一樣操作內(nèi)存空間的能力傍衡,這無疑也增加了程序發(fā)生相關(guān)指針問題的風險。在程序中過度负蠕、不正確使用Unsafe類會使得程序出錯的概率變大蛙埂,使得Java這種安全的語言變得不再“安全”,因此對Unsafe的使用一定要慎重遮糖。
Unsafe類為一單例實現(xiàn)绣的,提供靜態(tài)方法getUnsafe獲取Unsafe實例,當且僅當調(diào)用getUnsafe方法的類為引導(dǎo)類加載器所加載時才合法止吁,否則拋出SecurityException異常被辑。
如何獲取Unsafe實例?
1敬惦、從getUnsafe方法的使用限制條件出發(fā)盼理,通過Java命令行命令-Xbootclasspath/a把調(diào)用Unsafe相關(guān)方法的類A所在jar包路徑追加到默認的bootstrap路徑中,使得A被引導(dǎo)類加載器加載俄删,從而通過Unsafe.getUnsafe方法安全的獲取Unsafe實例宏怔。
2、通過反射獲取單例對象theUnsafe畴椰。
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeInstance {
public static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Unsafe對象中使用CAS
public class AtomicStudentAgeUpdater {
private String name ;
private volatile int age;
private static final Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset(AtomicStudentAgeUpdater.class.getDeclaredField("age"));
} catch (Exception e) {
throw new Error(e);
}
}
public void compareAndSwapAge(int old,int target){
unsafe.compareAndSwapInt(this,valueOffset,old,target);
}
public AtomicStudentAgeUpdater(String name,int age){
this.name = name;
this.age = age;
}
public int getAge(){
return this.age;
}
public static void main(String[] args) {
AtomicStudentAgeUpdater updater = new AtomicStudentAgeUpdater("張三",18);
updater.compareAndSwapAge(18,17);
}
}
Unsafe線程調(diào)度
static Object object = new Object();
Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();
//加鎖解鎖
unsafe.monitorEnter(object);
//同步塊
unsafe.monitorExit(object);
//線程阻塞臊诊、喚醒
unsafe.park();
unsafe.unpark();
Unsafe內(nèi)存屏障,防止指令重排
UnsafeInstance.reflectGetUnsafe().loadFence();//讀屏障
UnsafeInstance.reflectGetUnsafe().storeFence();//寫屏障
UnsafeInstance.reflectGetUnsafe().fullFence();//讀寫屏障