多個線程對index變量(共享變量/資源)同時操做引發的,再JDK1.5之前,要解決這個問題須要使用synchronized關鍵字,synchronized提供了一種排他的機制,同一個時間只能有一個線程執行某些操做。java
synchronized關鍵字能夠實現一個簡單的策略來放置線程干擾和內存一致行錯誤,若是一個對象多個線程是可見的,那麼該對象全部讀或者些都將經過同步的方式來進行。數據庫
synchronized關鍵字提供了一種鎖機制,可以確認保存共享變量的互斥訪問,從而放置數據不一致問題的出現。緩存
synchronized關鍵字包括monitor enter和monitor exit 兩個JVM指令,它可以保證再任什麼時候候任何線程執行到monitor enter成功以前都必須從主內存中獲取數據,而不是從緩存中,再monitor exit和運行成功以後,共享變量被更新後的值必須刷入主內存。安全
synchronized指令嚴格遵照java happens-before規則,一個monitor exit指令以前必需要由一個monitor enter。併發
syncronized關鍵字提供了一種互斥機制,也就是說在統一時刻,只能有一個線程訪問同步資源,synchronized是某線程取了與mutex關聯的monitor鎖. 例子:app
public class Demo { private final static Object MUTEX = new Object(); public void accessResource() { synchronized (MUTEX) { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { final Demo mutex = new Demo(); for (int i = 0; i < 5; i++) { new Thread(mutex::accessResource).start(); } } }
線程狀態查看: 方法一:jvm
方法二:ide
使用JDK自帶javap工具堆Demo class進行反編譯,輸出大量的JVM指令,在這些指令中,monitor enter和monitor exit是成對出現,有可能出現一個monitor,多個monitor exit,可是每一個monitor exit以前必須有對應的monitor enter.工具
PS E:\book\allCode\JavaConcurrency\target\classes\chapter_4\com\flexible> javap -c Demo 警告: 二進制文件Demo包含chapter_4.com.flexible.Demo Compiled from "Demo.java" public class chapter_4.com.flexible.Demo { public chapter_4.com.flexible.Demo(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void accessResource(); Code://獲取MUTEX 0: getstatic #2 // Field MUTEX:Ljava/lang/Object; 3: dup 4: astore_1 5: monitorenter//執行monitor enter jvm 6: getstatic #3 // Field java/util/concurrent/TimeUnit.MINUTES:Ljava/util/concurrent/TimeUnit; 9: ldc2_w #4 // long 10l 12: invokevirtual #6 // Method java/util/concurrent/TimeUnit.sleep:(J)V 15: goto 23//跳轉到23 18: astore_2 19: aload_2 20: invokevirtual #8 // Method java/lang/InterruptedException.printStackTrace:()V 23: aload_1 24: monitorexit //執行monitor exitjvm指令 25: goto 33 28: astore_3 29: aload_1 30: monitorexit 31: aload_3 32: athrow 33: return Exception table: from to target type 6 15 18 Class java/lang/InterruptedException 6 25 28 any 28 31 28 any public static void main(java.lang.String[]); Code: 0: new #9 // class chapter_4/com/flexible/Demo 3: dup 4: invokespecial #10 // Method "<init>":()V 7: astore_1 8: iconst_0 9: istore_2 10: iload_2 11: iconst_5 12: if_icmpge 42 15: new #11 // class java/lang/Thread 18: dup 19: aload_1 20: dup 21: invokevirtual #12 // Method java/lang/Object.getClass:()Ljava/lang/Class; 24: pop 25: invokedynamic #13, 0 // InvokeDynamic #0:run:(Lchapter_4/com/flexible/Demo;)Ljava/lang/Runnable; 30: invokespecial #14 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V 33: invokevirtual #15 // Method java/lang/Thread.start:()V 36: iinc 2, 1 39: goto 10 42: return static {}; Code: 0: new #16 // class java/lang/Object 3: dup 4: invokespecial #1 // Method java/lang/Object."<init>":()V 7: putstatic #2 // Field MUTEX:Ljava/lang/Object; 10: return }
1.Monitorenter 每一個對象都與一個monitor相關聯,一個monitor的lock的鎖只能被一個線程同時得到,在一個線程嘗試得到與對象關聯的monitor的全部權時會發生以下的事情。flex
若是monitor的計數器爲0,則覺得着該monitor的lock尚未被得到,某個線程得到以後將當即計數器加1.今後該線程就是這個monitor的全部者。
若是一個已經永遠了該monitor全部權的線程重入,則會致使monitor計數器再次加1
若是monitor已經被其餘線程所擁有,則其餘的線程嘗試獲取該monitor的全部權時,會被現如阻塞狀態知道monitor計數器變爲0,才能再次嘗試獲取堆monitor的全部權.
2.Monitorexit
釋放對monitor的全部權,想要釋放對某個對象關聯的monitor的全部權的前提時,你增長得到了全部權。釋放monitor的全部權的過程比較簡單,就是將monitor的計數器減1,若是計數器的結果爲0,那麼就覺得這個該線程再也不擁有對該monitor的全部權,通俗的講就是解鎖。
1.與monitor關聯的對象不能爲空(null)
2.synchronized做用域不要太大(好比直接將synchronized方法放在方法)
3.不一樣的monitor企圖鎖相同的方法 public class MyTask implements Runnable { private static final Object MUTEX = new Object(); @Override public void run() { synchronized (MUTEX){ //... System.out.println(Thread.currentThread().getName()); try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }
} public static void main(String[] args) { for (int i=0;i<5;i++){ new Thread(MyTask::new).start(); } } }
4.多鎖交叉致使死鎖
public class ThisMonitorDemo { public synchronized void method_1() { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void method_2() { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public void method_3() { synchronized (this) { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ThisMonitorDemo demo = new ThisMonitorDemo(); Thread t1 = new Thread(demo::method_1, "T1"); Thread t2 = new Thread(demo::method_2, "T2"); Thread t3 = new Thread(demo::method_2, "T3"); t1.start(); t2.start(); t3.start(); } }
執行的結果:
由上可知三個線程都是同一個實例鎖
在修飾類方法的時候使用的是類監視器而不是實例監視器,經過下面的例子就能夠看出來。
public class ClssMonitorDemo { public static synchronized void method_1() { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public static synchronized void method_2() { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public static void method_3() { synchronized (ClssMonitorDemo.class) { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ThisMonitorDemo demo = new ThisMonitorDemo(); Thread t1 = new Thread(ClssMonitorDemo::method_1, "T1"); Thread t2 = new Thread(ClssMonitorDemo::method_2, "T2"); Thread t3 = new Thread(ClssMonitorDemo::method_2, "T3"); t1.start(); t2.start(); t3.start(); } }
根據下圖的畫紅線的地方有java.lang.Class就能夠看出來了。
1.交叉鎖可致使程序出現死鎖 例如線程1獲取道L1鎖,等待獲取L2鎖,線程2得到了L2鎖,等待會的L1鎖,這種"哲學家們吃麪"的場景就會致使死鎖。
2.內存不足 在併發請求的系統可能內存時,若是內存時。可能會出現死鎖的狀況,線程1和線程2,若是線程一執行都各須要10M,而內存只有10M,那麼線程1和線程2都在等待對方釋放內存資源而致使死鎖。
3.一問一答式數據交換 服務段開啓某個端口,等待客戶訪問,客戶端發送請求當即等待接收,因爲某種緣由服務段錯過了客戶端的請求,仍然在等待一問以打的式的數據交換,此時,服務端和客戶端都在等待雙方發送數據。
4.數據庫鎖 不管數據庫表級鎖,仍是行級鎖,好比某個線程執行for update語句推出了事務,其餘線程訪問該數據庫時都會現如死鎖。
5.文件鎖 某線程得到文件鎖意外退出,其餘讀取該文件的線程也將會進入死鎖,知道系統釋放文件句柄資源。
6.死循環引發的死鎖。 程序因爲代碼緣由或者對某個一次處理不當,進入了死循環,雖然看線程堆棧信息不會發現任何死鎖的集鄉,可是程序不工做了,CPU的使用率居高不下,這種死鎖現象通常稱爲系統假死,這種狀況通常排查比較困難。
1.交叉鎖引發的死鎖 通常使用jstack -l PID 或者使用jconsole圖形界面上直接查看.
2.死循環引發的死鎖(假死) 通常使用jstack,jconsole,jvisualvm工具或者jProfiler(收費)進行檢查,四實查看沒法獲得很明顯的結果,由於線程沒有死鎖,只是一致runnbale並且CPU的使用率居高不下.