問題:線程安全問題的主要誘因是什麼?java
---》算法
一、存在共享數據(也稱臨界資源)緩存
二、存在多條線程共同操做這些共享數據安全
解決問題的根本方法:多線程
同一時刻有且只有一個線程在操做共享數據,其餘線程必須等到該線程處理完數據後再對共享數據進行操做(串行)app
互斥鎖的特性異步
一、互斥性:在同一時間只容許一個線程持有某個對象鎖,經過這種特性來實現多線程的協調機制,這樣在同一實際只有一個線程對學院同步的代碼塊(複合操做)進行訪問。互斥性也成爲操做的原子性。jvm
二、可見性:必須確保在鎖被釋放以前,對共享變量所作的修改,對於隨後得到該鎖的另外一個線程是可見的(則在得到鎖時應得到最新共享變量的值),不然另外一個線程多是在本地緩存的某個副本上繼續操做,從而引發不一致async
注意的是:synchronized鎖的不是代碼,鎖的都是對象ide
根據獲取的鎖的分類:獲取對象鎖和獲取類鎖
獲取對象鎖的兩種用法:
一、同步代碼塊(synchronized(this),synchronized(類實例對象)),鎖時小括號中的實例對象
二、同步非靜態方法(synchronized method), 鎖時當前對象的實例對象
獲取類鎖的兩種方法:
一、同步代碼塊(synchronized(類.class)),鎖是小括號()中的類對象(Class對象)
二、同步靜態方法(synchronized static method) ,鎖是當前對象的類對象(Class對象)
public class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { async(); } else if (threadName.startsWith("B")) { syncObjectBlock1(); } else if (threadName.startsWith("C")) { syncObjectMethod1(); } else if (threadName.startsWith("D")) { syncClassBlock1(); } else if (threadName.startsWith("E")) { syncClassMethod1(); }else if(threadName.startsWith("F")){ syncObjectMethod2(); } } /** * 異步方法 */ private void async() { try { System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 方法中有 synchronized(this|object) {} 同步代碼塊 */ private void syncObjectBlock1() { System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (this) { try { System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * synchronized 修飾非靜態方法 */ private synchronized void syncObjectMethod1() { System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void syncObjectMethod2() { // 注意這裏的syncObjectMethod2方法和syncObjectMethod1方法鎖的對象實例都是同一個!!! System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } private void syncClassBlock1() { System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (SyncThread.class) { try { System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } private synchronized static void syncClassMethod1() { System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class SyncDemo { public static void main(String... args) { SyncThread syncThread = new SyncThread(); Thread A_thread1 = new Thread(syncThread, "A_thread1"); Thread A_thread2 = new Thread(syncThread, "A_thread2"); Thread B_thread1 = new Thread(syncThread, "B_thread1"); Thread B_thread2 = new Thread(syncThread, "B_thread2"); Thread C_thread1 = new Thread(syncThread, "C_thread1"); Thread C_thread2 = new Thread(syncThread, "C_thread2"); Thread D_thread1 = new Thread(syncThread, "D_thread1"); Thread D_thread2 = new Thread(syncThread, "D_thread2"); Thread E_thread1 = new Thread(syncThread, "E_thread1"); Thread E_thread2 = new Thread(syncThread, "E_thread2"); Thread F_thread1 = new Thread(syncThread, "F_thread1"); Thread F_thread2 = new Thread(syncThread, "F_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); D_thread1.start(); D_thread2.start(); E_thread1.start(); E_thread2.start(); F_thread1.start(); F_thread2.start(); } }
總結:
一、有線程訪問對象的同步代碼塊時,另外的線程能夠訪問該對象的非同步代碼塊
二、若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另外一個訪問對象的同步代碼塊的線程會被阻塞
三、若鎖住的是同一個對象,一個線程在訪問對象的同步方法時,另外一個訪問對象同步方法的線程會被阻塞
四、若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另外一個訪問對象同步方法的線程會被阻塞,反之亦然
五、同一個類的不一樣對象的對象鎖互不干擾
六、類鎖因爲也是一種特殊的對象鎖,所以表現和上述1,2,3,4一致,而因爲一個類只有一把對象鎖,因此同一個類的不一樣對象使用類鎖將會是同步的
七、類鎖和對象鎖互補干擾
JVM優化鎖的策略有哪一些?
自旋鎖
一、許多狀況下,共享數據的鎖定狀態持續時間較短,切換線程不值得
二、經過讓線程執行忙循環等待鎖的釋放,不讓出CPU
三、缺點:若鎖被其餘線程長時間佔用,會帶來許多性能上的開銷
自適應自旋鎖
一、自旋的次數再也不固定
二、由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定
(JVM會愈來愈聰明,預測的時間愈來愈精確)
鎖消除
更完全的優化
一、JIT編譯時,對運行上下文進行掃描,去除不可能存在競爭的鎖
public class StringBufferWithoutSync {
public void add(String str1, String str2) {
//StringBuffer是線程安全,因爲sb只會在append方法中使用,不可能被其餘線程引用
//所以sb屬於不可能共享的資源,JVM會自動消除內部的鎖
StringBuffer sb = new StringBuffer();
sb.append(str1).append(str2);
}
public static void main(String[] args) {
StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
for (int i = 0; i < 1000; i++) {
withoutSync.add("aaa", "bbb");
}
}
}
鎖粗化
另外一種極端
一、經過擴大加鎖的範圍,避免反覆加鎖和解鎖
public class CoarseSync { public static String copyString100Times(String target){ int i = 0; StringBuffer sb = new StringBuffer(); while (i<100){ sb.append(target);
// 因爲append是同步方法,循環調用這個同步方法,
//會致使鎖的不斷加鎖和釋放鎖的操做,影響沒必要要的資源浪費。
//而這個時候jvm會把鎖進行粗化,粗化到整塊區域(紅色標記) } return sb.toString(); } }
自旋鎖,自適應自旋鎖,鎖消除,鎖粗化
synchronized的四種狀態
一、無鎖、偏向鎖、輕量級鎖、重量級鎖
鎖膨脹方向:無鎖---》偏向鎖---》輕量級鎖----》重量級鎖
偏向鎖:減小同一線程獲取鎖的代價
一、大多數狀況下,鎖不存在多線程競爭,老是由同一線程屢次得到
核心思想:若是一個線程得到了鎖,那麼鎖就進入偏向模式,此時Mark Word的結構也變爲偏向鎖結構,當該線程再次請求鎖時,無需再作任何同步操做,則獲取鎖的過程只須要檢查Mark Word的鎖標記位爲 偏向鎖以及當前線程Id等於Mark Word 的ThreadID便可,這樣就省去了大量有關鎖申請的操做。
不適合於鎖競爭比較激烈的多線程場合
輕量級鎖
輕量級鎖時由偏向鎖升級來的,偏向鎖運行在一個線程進入同步塊的狀況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級爲輕量級鎖
適應的場景:線程交替執行同步塊
若存在同一時間訪問同一鎖的狀況,就會到只輕量級鎖膨脹爲重量級鎖
問題:輕量級鎖的加鎖過程和減鎖過程是怎樣的?
----》
鎖的內存語義
當線程釋放鎖時,Java內存模型會把該線程對應的本地內存中的共享變量刷新到主內存中;
而當線程獲取鎖時,Java內存模型會把該線程對應的本地內存設置爲無效,從而使得被監控器包含的臨界區代碼必須從主內存中讀取共享變量
理解上:本地內存A就是指該A線程的棧幀裏面的displaced mark word ,因爲棧幀是線程私有的,其餘線程是沒法看到的,所以須要把這個值放到公共的地方,其餘線程才能看到,從而作出下一步動做。
CAS(Compare And Swap ) 能夠簡單地理解爲:是一種無鎖的算法