Java併發編程基礎(二)

線程安全與數據同步

synchronized關鍵字

多個線程對index變量(共享變量/資源)同時操做引發的,再JDK1.5之前,要解決這個問題須要使用synchronized關鍵字,synchronized提供了一種排他的機制,同一個時間只能有一個線程執行某些操做。java

synchronized關鍵字的解釋

synchronized關鍵字能夠實現一個簡單的策略來放置線程干擾和內存一致行錯誤,若是一個對象多個線程是可見的,那麼該對象全部讀或者些都將經過同步的方式來進行。數據庫

  • synchronized關鍵字提供了一種鎖機制,可以確認保存共享變量的互斥訪問,從而放置數據不一致問題的出現。緩存

  • synchronized關鍵字包括monitor enter和monitor exit 兩個JVM指令,它可以保證再任什麼時候候任何線程執行到monitor enter成功以前都必須從主內存中獲取數據,而不是從緩存中,再monitor exit和運行成功以後,共享變量被更新後的值必須刷入主內存。安全

  • synchronized指令嚴格遵照java happens-before規則,一個monitor exit指令以前必需要由一個monitor enter。併發

synchronized關鍵字的理解

線程堆棧分析

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

JVM指令分析

使用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的全部權,通俗的講就是解鎖。

使用synchronized須要注意的問題

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.多鎖交叉致使死鎖

This Monitor和Class Monitor的詳細介紹

This monitor

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();
	}
}

執行的結果:

由上可知三個線程都是同一個實例鎖

class monitor

在修飾類方法的時候使用的是類監視器而不是實例監視器,經過下面的例子就能夠看出來。

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的使用率居高不下.

相關文章
相關標籤/搜索