並發編程的3個重要概念
1.原子性:
一個操作或者多個操作,要么全部成功,要么全部失敗
1.java中保證了基本數據類型的讀取和賦值,保證了原子性,這些操作不可終端
a=10 原子性 b=a 不滿足 1.read a; 2.assign b; c++ 不滿足 1.read c; 2 add 3.assige to c c=c+1 不滿足 1.read c 2.add; 3. assign to c;
2.可見性:
volatile 可以保證可見性.主要是把變量放在主存里
多個線程訪問這個變量,一個線程修改之后必須保證另一個線程可以看見。
每一個線程都自己的緩存,有的變量在主存區,我們要保證變量的可見性
4.順序性:
java中hapens-before 原則保證了有序性
4.1 代碼順序(程序順序規則): 單個線程中的每個操作 編寫在前面的反生在編寫在后面的
4.2 lock原則(監視器鎖規則) : unlock 必須發生在lock之后
4.3 volatile變量規則: 對該變量的寫操作必須發生在讀操作之前.就是一個變量 寫變量發生在讀變量之前
4.4 傳遞規則: a->b->c 那么a肯定在c之前
4.5 start規則: 先start 線程在啟動(個人認為廢話)
4.6 中斷規則: 對線程的中斷 先發生在 捕獲interpetor異常之前(個人認為廢話)
4.7 對象的銷毀規則: 一個對象的初始化.必須發生在finalize 之前(個人認為廢話)
4.8 線程的終結規則: 所有操作都發生在線程死亡之前 (個人認為廢話)
比如定義一個變量
int i =0; boolean flag =false; int i =1; boolean flag =true; java中有重排序的過程 因為這幾條代碼沒有關聯性可能會變成 int i =0; boolean flag =false; boolean flag =true; int i =1;
重排序只保證最終一致性,重排序也是有依賴關系的,重排序對沒有依賴關系的可以重排序 ,重排序不會影響單線程,但是會影響多線程
舉個例子
--------thread-1 ==========
obj =createObj();1
flag = true; 2
如果這里發生指令重排將 1 -2 順序顛倒下面下面的代碼就會報出空指針異常
-----thread-2------
while(!flag){
}
useObject()
atomic 具有原子性
主要有倆點 1.他的變量是volatile
2.unsafe方法調用
// setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value;
什么時候需要使用atomic類對象
當多線程操作一個變量的時候。如果使用傳統的變量會出錯,很多同學為了解決這個問題,加了votatile修飾。也並不能夠解決問題。
補充下votalite 的知識
volatile具有可見性、有序性,不具備原子性。
當value+=1時候我們需要做幾個操作
1.從主存區獲取變量到寫存區
2.value+1
3.value = 新的value
4.講變量寫入主存區
這4步都是原子性的。但是在多線程環境中。可能你讀取到的變量是別人修改過的變量造成線程不安全情況

private static Set<Integer> set = Collections.synchronizedSet(new HashSet<>()); public static void main(String[] args) throws InterruptedException { Thread t1= new Thread(()->{ int x=0; while (x<500){ set.add(value); int temp =value; System.out.println(Thread.currentThread().getName()+" "+temp); value +=1; x++; } }); Thread t2= new Thread(()->{ int x=0; while (x<500){ set.add(value); int temp =value; System.out.println(Thread.currentThread().getName()+" "+temp); value +=1; x++; } }); Thread t3= new Thread(()->{ int x=0; while (x<500){ set.add(value); int temp =value; System.out.println(Thread.currentThread().getName()+" "+temp); value +=1; x++; } }); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println(set.size()); 」
當我們使用aomic類時。可以保證原子性
AtomicInteger atoValue = new AtomicInteger(); Thread t1= new Thread(()->{ int x=0; while (x<500){ int i = atoValue.getAndIncrement(); set.add(i); System.out.println(Thread.currentThread().getName()+" "+i); atoValue.getAndIncrement(); x++; } }); Thread t3= new Thread(()->{ int x=0; while (x<500){ int i =atoValue.getAndIncrement(); set.add(i); System.out.println(Thread.currentThread().getName()+" "+i); atoValue.getAndIncrement(); x++; } }); Thread t2= new Thread(()->{ int x=0; while (x<500){ int i = atoValue.getAndIncrement(); set.add(i); System.out.println(Thread.currentThread().getName()+" "+i); x++; } }); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); System.out.println(set.size());
下載jdk源碼
http://download.java.net/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip
源碼查看
/openjdk/jdk/src/share/classes/sun/misc/Unsafe.java
atomic保證原子性操作是因為有unsafe.java
很多人都說unsafe.java 是java留的一后門可以直接操作cpu級別的內存
因為UnSafe提供了硬件級別的原子操作,提高了Java對底層操作的能力。Unsafe類使Java語言擁有了像C語言的指針一樣的操作內存空間的能力
一篇比較好的博客
https://www.cnblogs.com/pkufork/p/java_unsafe.html
如何保證原子性
/** * Atomically adds the given value to the current value of a field * or array element within the given object <code>o</code> * at the given <code>offset</code>. * * @param o object/array to update the field/element in * @param offset field/element offset * @param delta the value to add * @return the previous value * @since 1.8 */
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
1、需要修改的對象
2、更改屬性的內存偏移量
4、要添加的 1
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
}
while (!compareAndSwapInt(o, offset, v, v + delta)); return v;
}
其實主要原子性是通過compareAndSwapInt對比算法.傳入你開始的值.和預期的值 如果不相同就一直執行下去直到相同為止,達到很多人說的無鎖概念,其實我更傾向於自旋鎖
假設多線程調用getAndIncrement
假設初始值是1
compareAndWapInt(1,1+1) |
compareAndWapInt(1,1+1) |
1 2 比較false 進行下一次 2==2 2 |
1 2比較 false 2 3 比較false 3 |
這樣就實現了free lock
獲取unsafe.實例利用java反射
Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null);
自己測試類
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Field f1 = Unsafe.class.getDeclaredField("theUnsafe"); f1.setAccessible(true); Unsafe unsafe = (Unsafe) f1.get(null); Class clazz = Target.class; Field[] fields = clazz.getDeclaredFields(); for (Field f : fields) { // 獲取屬性偏移量,可以通過這個偏移量給屬性設置 System.out.println(f.getName() + ":" + unsafe.objectFieldOffset(f)); } Target target = new Target(); Field intFiled = clazz.getDeclaredField("intParam") ; // int a=(Integer)intFiled.get(target ) ; // System.out.println("原始值是:"+a); //intParam的字段偏移是12 原始值是3 我們要改為10 System.out.println(unsafe.compareAndSwapInt(target, 12, 3, 10)); int b=(Integer)intFiled.get(target) ; System.out.println("改變之后的值是:"+b); // // //這個時候已經改為10了,所以會返回false System.out.println(unsafe.compareAndSwapInt(target, 12, 3, 10)); // // System.out.println(unsafe.compareAndSwapObject(target, 24, null, "5")); } } class Target { int intParam=3; long longParam; String strParam; String strParam2; }