原子操做:不能被分割(中斷)的一個或一系列操做叫原子操做。java
原子操做Atomic主要有12個類,4種類型的原子更新方式,原子更新基本類型,原子更新數組,原子更新字段,原子更新引用。Atomic包中的類基本都是使用Unsafe實現的包裝類。面試
基本類型:AtomicInteger,AtomicLong,AtomicBoolean;apache
引用類型:AtomicReference、AtomicReference的ABA實例、AtomicStampedRerence、AtomicMarkableReference;bootstrap
數組類型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray;數組
屬性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater;安全
下面咱們來看一下每種類型的一個實例:多線程
/**
* <p>Title: AtomicIntegerTest.java</p >
* <p>Description: </p >
* <p>Copyright: NTT DATA Synergy All Rights Reserved.</p >
* <p>Company: www.synesoft.com.cn</p >
* <p>@datetime 2019年8月9日 上午8:01:30</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test;
import java.util.concurrent.atomic.AtomicInteger; /** * @author hong_liping * */ public class AtomicIntegerTest { static AtomicInteger ai=new AtomicInteger(); public static void main(String[] args) { for(int i=0;i<10;i++){ new Thread(new Runnable() { @Override public void run() { ai.incrementAndGet(); } }).start(); } // try { // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println("循環後的結果以下:"+ai.get()); } }
//測試結果
循環後的結果以下:9
循環後的結果以下:10
根據上面的代碼,咱們多運行幾回,會發現,代碼的測試結果一下子是9一下子是10,不是10,爲何呢,由於線程尚未跑完,我下面的就已經打出來了,讓線程睡眠一下就能夠解決這個問題了。併發
下面咱們來看一下atomic的ABA問題,這個問題在面試的時候常常問到。框架
/**
* <p>Title: AtomicTest.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月8日 下午3:40:37</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test;
import java.util.concurrent.atomic.AtomicInteger; /** * @author hong_liping * */ public class AtomicAbaTest { private static AtomicInteger ato=new AtomicInteger(1); public static void main(String[] args) { Thread mainT=new Thread(new Runnable() { @Override public void run() { int a=ato.get(); System.out.println(Thread.currentThread().getName()+"原子操做修改前數據"+a); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } boolean successFlag=ato.compareAndSet(a, 2); if(successFlag){ System.out.println(Thread.currentThread().getName()+"原子操做修改後數據"+ato.get()); } } },"mainT"); Thread otherT=new Thread(new Runnable() { @Override public void run() { int b=ato.incrementAndGet();//1+1 System.out.println(Thread.currentThread().getName()+"原子操做自增後數據"+b); b=ato.decrementAndGet();//2-1 System.out.println(Thread.currentThread().getName()+"原子操做自減後數據"+b); } },"OtherT"); mainT.start(); otherT.start(); } }
測試結果:
OtherT原子操做自增後數據2
mainT原子操做修改前數據1
OtherT原子操做自減後數據1
mainT原子操做修改後數據2ide
根據上面的操做,咱們能夠看到的是AtomicInteger的操做自增,自減,值的替換等。可是此處應當注意的是原子操做存在一個ABA問題,ABA問題的現象就是:mainT執行完成後的值2(替換的2),otherT在執行2-1的時候的2是自增(1+1)的結果。在這兩個線程中用到的2不是同一個2,就至關因而一個漏洞,至關於說你從王健林帳號中偷走了10個億去投資,等你投資好了回本了,你再把這10個億打回了王健林帳號,這整個過程王建林沒有發現,你的整個操做過程也沒有記錄,因此對於王健林來講他的錢沒有丟失過,仍是放在那裏的。很明顯要解決這個ABA問題最好的辦法就是每一步操做都打個標記,至關於一個銀行的流水,這樣你偷錢,還錢的整個過程就有一個出,一個入,王健林看的時候就會發現個人總金沒有變,可是操做記錄顯示個人錢曾經被人盜了而後又被人還回來了。這就須要用到AtomicStampeReference.
接下來咱們來看一下AtomicStampedReference的測試類:
/**
* <p>Title: AtomicStampedReference.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月9日 上午8:35:56</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicStampedReference; /** * @author hong_liping * */ public class AtomicStampedReferenceTest { private static AtomicStampedReference<Integer> asf=new AtomicStampedReference<Integer>(1, 0); public static void main(String[] args) { Thread mainT=new Thread(new Runnable() { @Override public void run() { int stamp= asf.getStamp(); System.out.println(Thread.currentThread().getName()+"原子操做修改前數據"+asf.getReference()+ "_"+stamp); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //此時expectedReference未發生改變,可是stamp已經被修改了,因此CAS失敗 boolean successFlag=asf.compareAndSet(1, 2, stamp, stamp+1); if(successFlag){ System.out.println(Thread.currentThread().getName()+"原子操做修改後數據"+asf.getReference()+ "_"+stamp); }else{ System.out.println(Thread.currentThread().getName()+"cas操做失敗"); } } },"mainT"); Thread otherT=new Thread(new Runnable() { @Override public void run() { int stamp=asf.getStamp(); asf.compareAndSet(1, 2, stamp, stamp+1); System.out.println(Thread.currentThread().getName()+"原子操做自增後數據"+asf.getReference()+ "_"+asf.getReference()); asf.compareAndSet(2, 1, stamp, stamp+1); System.out.println(Thread.currentThread().getName()+"原子操做自減後數據"+asf.getReference()+ "_"+stamp);; } },"OtherT"); mainT.start(); otherT.start(); } } //測試結果: mainT原子操做修改前數據2_0 OtherT原子操做自增後數據2_2 OtherT原子操做自減後數據2_0 mainTcas操做失敗
接下來咱們來看一下AtomicIntegerArray的一個案例
/**
* <p>Title: AtomicArrayTest.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月10日 上午9:45:49</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test;
import java.util.concurrent.atomic.AtomicIntegerArray; import com.sun.org.apache.bcel.internal.generic.NEWARRAY; /** * @author hong_liping * */ public class AtomicArrayTest { static int[] array=new int[]{1,2,3}; static AtomicIntegerArray aia=new AtomicIntegerArray(array); public static void main(String[] args) { aia.getAndSet(1, 5); System.out.println(aia.get(1)); System.out.println(array[1]); if(aia.get(1)==array[1]){ System.out.println("數組中的值與原子數組中的相等"); }else{ System.out.println("數組中的值與原子數組中的不相等"); } } }
結果:
5
2
數組中的值與原子數組中的不相等
由以上的代碼能夠看出原子數組與我自己定義的數據同一個下標下的值是不同的,爲何呢,咱們看一下源碼就會發現原子數據操做的並非我定義的變量自己,而是先拷貝一份,而後操做的是拷貝的版本。
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();//初始化數組的時候拷貝 }
public final int getAndSet(int i, int newValue) {
return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue); }
在進行數據原子操做的時候使用的是魔術類Unsafe.
接下來咱們再來看看AtomicIngerFieldUpdater
/**
* <p>Title: AtomicIntegerFieldUpdateTest.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月10日 上午10:02:22</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * @author hong_liping * */ public class AtomicIntegerFieldUpdateTest { static AtomicIntegerFieldUpdater aifu=AtomicIntegerFieldUpdater.newUpdater(Person.class, "age"); static class Person{ private String name; public volatile int age; public Person(String name,int age){ this.name=name; this.age=age; } public int getAge(){ return age; } } public static void main(String[] args) { Person person=new Person("張三", 18); System.out.println(aifu.getAndIncrement(person)); System.out.println(aifu.get(person)); } }
測試結果:
18
19
在age屬性上加volatile是爲了保證在多線程併發的狀況下保證可見性。
Unsafe是位於sun.misc包下的一個類,主要提供一些用於執行低級別、不安全操做的方法,如直接訪問系統內存資源、自主管理內存資源等,這些方法在提高Java運行效率、加強Java語言底層資源操做能力方面起到了很大的做用。 Unsafe類爲一單例實現,提供靜態方法getUnsafe獲取Unsafe實例,當且僅當調用getUnsafe方法的類爲引導類加載器所加載時才合法,不然拋出SecurityException異常。
@CallerSensitive
/* */ public static Unsafe getUnsafe() /* */ { /* 88 */ Class localClass = Reflection.getCallerClass(); /* 89 */ if (!VM.isSystemDomainLoader(localClass.getClassLoader()))// 僅在引導類加載器`BootstrapClassLoader加載時才合法 /* 90 */ throw new SecurityException("Unsafe"); /* 91 */ return theUnsafe; /* */ } /* */
Unsafe常常用到的就是CAS,內存屏障(禁止load,store從新排序),線程調度(線程掛起,恢復還有獲取,釋放鎖)。
如何獲取Unsafe,一、把調用Unsafe相關方法的類Demo所在jar包路徑追加到默認的bootstrap路徑中,使得A被引導類加載器加載 java -Xbootclasspath/Demo:${path} // 其中path爲調用Unsafe相關方法的類所在jar包路徑
二、經過反射獲取單例對象theUnsafe
咱們能夠看一下下面的一個代碼:
public class UnsafeInstance {
public static Unsafe reflectGetUnsafe(){ Field field; try { field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } return null; } }
接下來再來看一個利用Unsafe的代碼:
/**
* <p>Title: AtomicUnsafeUpdaterTest.java</p >
* <p>Description: </p >
* <p>@datetime 2019年8月10日 上午10:57:23</p >
* <p>$Revision$</p >
* <p>$Date$</p >
* <p>$Id$</p >
*/
package com.test;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import sun.misc.Unsafe; /** * @author hong_liping * */ public class AtomicUnsafeUpdaterTest { private String name; private volatile int age; private static final Unsafe unsafe=UnsafeInstance.reflectGetUnsafe(); private static final long valueOffset; static{ try { valueOffset=unsafe.objectFieldOffset(AtomicUnsafeUpdaterTest.class.getDeclaredField("age"));//偏移量 System.out.println("initial valueOffset is "+valueOffset); } catch (Exception e) { throw new Error(e); } } public void compareAndSwapAge(int old,int target){ unsafe.compareAndSwapInt(this, valueOffset, old, target); } public AtomicUnsafeUpdaterTest(String name,int age){ this.name=name; this.age=age; } public int getAge(){ return this.age; } public static void main(String[] args) { AtomicUnsafeUpdaterTest test=new AtomicUnsafeUpdaterTest("美女",30); test.compareAndSwapAge(30, 25); System.out.println("年齡變換後的值爲"+test.getAge()); } }
/** * CAS
* @param o 包含要修改field的對象
* @param offset 對象中某field的偏移量
* @param expected 指望值
* @param update 更新值
* @return true | false */
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
上述中的偏移量是什麼呢,咱們來看一下:AtomicUnsafeUpdaterTest的實現中,靜態字段valueOffset即爲字段value的內存偏移地址,valueOffset的值在AtomicInteger初始化時,在靜態代碼塊中經過Unsafe的objectFieldOffset方法獲取。在AtomicInteger中提供的線程安全方法中,經過字段valueOffset的值能夠定位到AtomicUnsafeUpdaterTest對象中value的內存地址,從而能夠根據CAS實現對value字段的原子操做。
public class ThreadParkerTest {
public static void main(String[] args) { /*Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("thread - is running----"); LockSupport.park();//阻塞當前線程 System.out.println("thread is over-----"); } }); t.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.unpark(t);//喚醒指定的線程*/ //拿出票據使用 LockSupport.park(); System.out.println("main thread is over"); //至關於先往池子裏放了一張票據 LockSupport.unpark(Thread.currentThread());//Pthread_mutex System.out.println("im running step 1"); } }
public class ObjectMonitorTest {
static Object object = new Object(); /* public void method1(){ unsafe.monitorEnter(object); } public void method2(){ unsafe.monitorExit(object); }*/ public static void main(String[] args) { /*synchronized (object){ }*/ Unsafe unsafe = UnsafeInstance.reflectGetUnsafe(); unsafe.monitorEnter(object);//獲取鎖 //業務邏輯寫在此處之間 unsafe.monitorExit(object);//鎖釋放 }
public class FenceTest {
public static void main(String[] args) { UnsafeInstance.reflectGetUnsafe().loadFence();//讀屏障 UnsafeInstance.reflectGetUnsafe().storeFence();//寫屏障 UnsafeInstance.reflectGetUnsafe().fullFence();//讀寫屏障 } }
以上就是關於原子操做和Unsafe的解讀,歡迎留言評論,謝謝。