線程的使用帶來了很是多的便利同時,也給咱們帶來了不少困擾java
當多個線程訪問某個對象時,無論運行時環境採用何種調度方式或者這些線程將如何交替執行,而且在主調代碼中不須要任何額外的同步或者協同,這個類都能表現出正確的行爲,那麼就稱這個類是線程安全的數組
一、原子性緩存
二、可見性安全
三、有序性架構
一、CPU 增長了告訴緩存,均衡與內存的速度差別app
二、操做系統增長進程、線程、以及分時複用 CPU,均衡 CPU 與 I/O 設備的速度差別函數
三、編譯程序優化指令的執行順序,使得可以更加合理的利用緩存工具
public class AtomicDemo{ public static int count=0; public static void incr(){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } count++; //count++ (只會由一個線程來執行) } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { new Thread(AtomicDemo::incr).start(); } Thread.sleep(4000); System.out.println("result:"+count); } }
JMM優化
Java內存模型是一種抽象結構,它提供了合理的禁用緩存以及禁止重排序的方法來解決可見性,有序性問題this
一、Volatile、synchronized、final 關鍵字
二、Happens-Before 原則
鎖的範圍
一、對於普通同步方法,鎖是當前實例對象。
二、對於靜態同步方法,鎖是當前類的Class對象。
三、對於同步方法塊,鎖是 Synchonized 括號裏配置的對象。
//對象鎖(同一對象生效),鎖的是this public class SyncDemo { //對象鎖 public synchronized void demo(){} public static void main(String[] args) { SyncDemo syncDemo1 = new SyncDemo(); SyncDemo syncDemo2 = new SyncDemo(); //沒法實現兩個線程的互斥 new Thread(()->{ syncDemo.demo(); }).start(); new Thread(()->{ // syncDemo1.demo(); syncDemo.demo();//BLOCKED狀態 }).start(); } }
//靜態同步方法(類級別的鎖),鎖的是 SyncDemo.class public class SyncDemo { //靜態同步方法, public synchronized static void demo(){ } public static void main(String[] args) { SyncDemo syncDemo = new SyncDemo(); SyncDemo syncDemo1 = new SyncDemo(); //若是synchronized爲靜態方法,那麼下面兩個線程會互存在斥 new Thread(()->{ syncDemo.demo(); }).start(); new Thread(()->{ syncDemo1.demo(); }).start(); } }
//同步方法塊,能夠是對象鎖,也能夠是類級別鎖,可是範圍是可控的。 public class SyncDemo { public void demo(){ //TODO synchronized (this){//對象鎖 } //TODO } public void demo1(){ //TODO synchronized (SyncDemo.class){//類級別的鎖 } //TODO } }
一、以上出現的鎖就只有兩把,一個是對象鎖,一個是類級別鎖。
二、synchronized方法塊中鎖的範圍是可控的
synchronized 的優化
一、自適應自旋鎖
二、引入偏向鎖、輕量級鎖
三、鎖消除、鎖粗化
volatile 是能夠用來解決可見性和有序性問題的
Lock指令的做用
一、將當前處理器緩存行的數據寫回到系統內存
二、這個寫回內存的操做會使在其餘CPU裏緩存了該內存地址的數據無效
什麼狀況下須要用到volatile
當存在多個線程對同一個共享變量進行修改的時候,須要增長volatile,保證數據修改的實時可見
public class VisableDemo { //volatile解決[可見性] public volatile static boolean stop=false; public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(()->{ int i=0; while(!stop){ i++; } System.out.println("result:"+i); }); thread.start(); System.out.println("begin start thread"); Thread.sleep(1000); stop=true; //主線程中修改stop的值 } }
//volatile 解決 [有序性] value = 3; void exeToCPU0(){ value = 10; isFinsh = true; } void exeToCPU1(){ if(isFinsh){ assert value == 10; } } /* CPU層面的內存屏障,(X86架構裏面提供了三種內存屏障) 一、Store Barrier[寫],強制全部在 store 屏障指令以前的 store 指令,都在該 store 屏障指令執行以前被執行,並把 store 緩衝區的數據都刷到 CPU 緩存 二、Load Barrier[讀],強制全部在 load 屏障指令以後的 load 指令,都在該 load 屏障指令執行以後被執行,而且一直等到 load 緩衝區被該 CPU 讀完才能執行以後 load 指令 三、Full Barrier[全],複合了 load 和 store 屏障的功能 */ value = 3; void exeToCPU0(){ value = 10; storebarrier();//寫屏障,讓value 與 isFinsh 沒法指令重排 isFinsh = true; } void exeToCPU1(){ if(isFinsh){ loadbarrier();//讀屏障,確保value必定是10[最新值] assert value == 10; } }
本質上來講:volatile 其實是 經過內存屏障來防止指令重排序 以及 禁止 cpu 高速緩存來解決可見性問題。
而 #Lock 指令,它本意上是禁止高速緩存解決可見性問題,但實際上在這裏,它表示的是一種內存屏障的功能。也就是說針對當前的硬件環境, JMM 層面採用 Lock 指令做爲內存屏障來解決可見性問題。
final 在 Java中是一個保留的關鍵字,能夠聲明成員變量、方法、類以及本地變量。一旦你將引用聲明作 final,你將不能改變這個引用了
final域 與 線程安全 有什麼關係?
對於 final 域,編譯器 和 處理器 要遵照兩個重排序規則
1)在構造函數內對一個 final 域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操做之間不能重排序。
2)初次讀一個包含 final 域的對象的引用,與隨後初次讀這個final域,這兩個操做之間不能重排序。
//寫 final 域得重排序規則 public class FinalExample{ int i; //普通變量 final int j; //final變量 static FinalExample obj; public FinalExample(){ //構造函數 i = 1; //寫普通域 j = 2; //寫 final 域 } public static void writer(){//寫線程 A 執行 obj = new FinalExample(); } public static void reader(){ //讀線程 B 執行 FinalExample object = obj; //都對象引用 int a = object.i; //讀普通域 int b = object.j; //讀 fianl 域 } }
寫final域重排序規則
一、JMM禁止編譯器把final域的寫重排序到構造函數以外。
二、編譯器會在 final 域的寫以後,構造函數 return 以前,插入一個 StoreStore 屏障。這個屏障禁止處理器把final域的寫重排序到構造函數以外
讀域的重排序規則
在一個線程中,初次讀對象引用與初次讀該對象包含的 final 域,JMM 禁止處理器重排序這兩個操做,編譯器會在讀final域操做的前面插入一個 LoadLoad 屏障。
什麼是Happens-Before?
Happens-Before 是一種可見性規則,它表達的含義是前面一個操做的結果對後續操做是可見的。
A Happens-Before B ➡ A 得執行結果對 B 可見
6種 Happens-Before 規則 [ 天生知足可見性規則 ]
一、程序順序規則
二、監視器鎖規則
三、Volatile 變量規則
四、傳遞性
五、start() 規則
六、Join() 規則
/* as-if-serial 單線程中無論怎麼重排序,獲得的執行結果是不能改變的 編譯器/處理器都要遵照,所以不會對存在數據依賴關係的語句進行重排序 */ class VolatileExample{ int x = 0; volatile boolean v = false; public void write(){ x = 42; v = true; } public void reader(){ if(v == true){ //這裏x會是多少? } } }
對一個鎖的解鎖 Happens-Before 於後續對這個鎖的枷鎖
synchronized(this){//此處自動枷鎖 //x 是共享變量,初始值 = 10 if(this.x < 12){ this.x = 12; } }//此處自動解鎖
對一個 volatile 域的寫,Happens-Before 於任意後續對這個 volatile 域的讀。
其實是經過內存屏障來實現的
若是 A happens-before B,且 B happens-before C,那麼 A happens-before C
class VolatileExample{ int x = 0; volatile boolean v = false; public void write(){ x = 42; v = true; } public void reader(){ if(v == true){ //這裏x會是多少? } } }
若是線程A執行操做ThreadB.start()(啓動線程B),那麼A線程的ThreadB.start()操做happens-before於線程B中的任意操做
Thread B = new Thread(()->{ //主線程調用 B.start()以前 //全部對共享變量的修改,此處皆可見 //此例中,var == 77 }); //此處對共享變量 var 修改 var = 77; //主線程啓動子線程 B.start();
若是線程A執行操做 ThreadB.join() 併成功返回,那麼 線程B 中的任意操做 happens-before 於 線程A 從 ThreadB.join() 操做成功返回
Thread B = new Thread(()->{ //此處對共享變量var 修改 var = 66 }); //例如此處對共享變量修改 //則這個修改結果對線程B可見 //主線程啓動子線程 B.start(); B.join(); //子線程全部對共享變量的修改 //在主線程調用B.join()以後皆可見 //此例中,var == 66
原子性問題的解決方案
synchronized、Lock
J.U.C 包下的 Atomic 類 [ 無鎖工具的典範 ]
import java.util.concurrent.atomic.AtomicInteger; //atomic 保證原子性 public class AtomicDemo{ // public static int count=0; private static AtomicInteger atomicInteger=new AtomicInteger(0); public static void incr(){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // count++; //count++ (只會由一個線程來執行) atomicInteger.incrementAndGet(); } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { new Thread(AtomicDemo::incr).start(); } Thread.sleep(4000); // System.out.println("result:"+count); System.out.println("result:"+atomicInteger.get()); } }
Atomic 實現原理
一、Unsafe 類
二、CAS
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
Atomic 分類
一、原子更新基本類型
二、原子更新數組
三、原子更新引用類型
四、原子更新字段類
public class ThreadLocalDemo { private static Integer num=0; public static final ThreadLocal<Integer> local=new ThreadLocal<Integer>(){ protected Integer initialValue(){ return 0; //初始值 } }; public static final ThreadLocal<Integer> local1=new ThreadLocal<Integer>(); public static void main(String[] args) { Thread[] threads=new Thread[5]; //但願每一個線程都拿到的是0 for (int i = 0; i < 5; i++) { threads[i]=new Thread(()->{ // num+=5; int num=local.get(); //拿到初始值 local1.get(); num+=5; local.set(num); System.out.println(Thread.currentThread().getName()+"->"+num); },"Thread-"+i); } for(Thread thread:threads){ thread.start(); } } }
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
發佈的意思是使一個對象可以被當前範圍以外的代碼所使用
public static HashSet<Person> persons; public void init(){ persons = new HashSet<Person>(); }
//不安全發佈 private String[] states = {'a','b','c','d'}; //發佈出去一個 public String[] getStates(){ return states; } public static void main(String[] args){ App unSafePub = new App(); System.out.println("Init array is: "+Arrays.toString(unSafePub.getStates())); unSafePub.getStates()[0] = "Mic!"; System.out.println("After modify.the array is: "+ Arrays.toString(unSafePub.getStates())); }
也成爲對象的逃逸,一種錯誤的發佈,當一個對象尚未構造完成時,就使它被其餘線程所見
一、在靜態初始化函數中初始化一個對象引用
二、將對象的引用保存到volatile類型的域或者AtomicReference對象中(利用volatile happen-before規則)
三、將對象的引用保存到某個正確構造對象的final類型域中(初始化安全性)
四、將對象的引用保存到一個由鎖保護的域中(讀寫都上鎖)
//靜態初始化 public class StaticDemo { private StaticDemo(){} private static StaticDemo instance=new StaticDemo(); public static StaticDemo getInstance(){ return instance; } }
//final域 public class FinalDemo { private final Map<String,String> states; public FinalDemo(){ states=new HashMap<>(); states.put("mic","mic"); } }
//volatile public class VolatileSyncDemo { private VolatileSyncDemo(){} //DCL問題[加 volatile] private volatile static VolatileSyncDemo instance=null; public static VolatileSyncDemo getInstance(){ if(instance==null){ synchronized(VolatileSyncDemo.class) { if(instance==null) { instance = new VolatileSyncDemo(); } } } return instance; } /** * instance = new VolatileSyncDemo(); * -> * 1. memory=allocate() * 2. * 3. instance=memory * ctorInstance(memory) * * 1.3.2 (不完整實例) */ }