Java併發15:併發三特性-有序性定義、有序性問題與有序性保證技術
在Java併發程式設計中,如果要保證程式碼的安全性,則必須保證程式碼的原子性、可見性和有序性。
在 Java併發12:併發三特性-原子性、可見性和有序性概述及問題示例中,對併發中的三個特性(原子性、可見性和有序性)進行了初步學習。
本章主要就Java中保障有序性的技術進行更加全面的學習。
1.整體回顧
有序性定義:即程式執行的順序按照程式碼的先後順序執行。
Java自帶有序性:happens-before原則。
2.有序性問題
其他大牛們經常拿下面的程式碼作為有序性的示例:
//執行緒1: context = loadContext(); //語句1 inited = true; //語句2 //執行緒2: while(!inited ){ sleep() } doSomethingwithconfig(context);
不過本人並沒有通過實際程式設計執行,來證明此段程式的無序性。
為了更形象的理解有序性問題,本人使用了後面的示例,雖然後面的示例對有序性體現不夠準確。
如果各位看官,有更好的能夠實際體現有序性問題的示例,請一定告知,十分感謝!
場景說明:
有兩個執行緒A和執行緒B。
執行緒A對變數x進行加法和減法操作。
執行緒B對變數x進行乘法和出發操作。
程式碼:
這裡的示例只是為了方便得到無序的結果而專門寫到,所以有些奇特。
static String a1 = newString("A : x = x + 1"); static String a2 = new String("A : x = x - 1"); static String b1 = new String("B : x = x * 2"); static String b2 = new String("B : x = x / 2"); //不採取有序性措施,也沒有發生有序性問題..... LOGGER.info("不採取措施:單執行緒序列,視為有序;多執行緒交叉序列,視為無序。"); new Thread(() -> { System.out.println(a1); try{ Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a2); }).start(); new Thread(() -> { System.out.println(b1); System.out.println(b2); }).start();
執行結果:
2018-03-18 00:16:20 INFO ConcurrentOrderlyDemo:63 - 不採取措施:單執行緒序列,視為有序;多執行緒交叉序列,視為無序。 A : x = x + 1 B : x = x * 2 B : x = x / 2 A : x = x - 1
通過執行結果發現,多執行緒環境中,程式碼是交替的序列執行的,這樣會導致產生意料之外的結果。
3.有序性技術保障
在Java中提供了多種有序性保障措施,這裡主要涉及兩種:
通過synchronized關鍵字定義同步程式碼塊或者同步方法保障有序性。
通過Lock介面保障有序性。
3.1.synchronized關鍵字
定義一個物件用於同步塊:
//定義一個物件用於同步塊 static byte[] obj = new byte[0];
在多執行緒環境中進行synchronized關鍵字的有序性測試:
LOGGER.info("通過synchronized保證有序性:成功"); //通過synchronized保證有序性 new Thread(() -> { synchronized (obj) { System.out.println(a1); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a2); } }).start(); new Thread(() -> { synchronized (obj) { System.out.println(b1); System.out.println(b2); } }).start();
執行結果(多次):
2018-03-18 11:02:25 INFO ConcurrentOrderlyDemo:79 - 通過synchronized保證有序性:成功 A : x = x + 1 A : x = x - 1 B : x = x * 2 B : x = x / 2
通過多次執行,發現執行結果一致,所以可以確定synchronized關鍵字能夠保證程式碼的有序性。
3.2.Lock介面
定義一個Lock鎖:
//定義一個Lock鎖 static ReentrantLock reentrantLock = new ReentrantLock(true);
在多執行緒環境中進行Lock介面的有序性測試:
LOGGER.info("通過Lock保證有序性:成功"); //通過Lock保證有序性 new Thread(() -> { reentrantLock.lock(); System.out.println(a1); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a2); reentrantLock.unlock(); }).start(); new Thread(() -> { reentrantLock.lock(); System.out.println(b1); System.out.println(b2); reentrantLock.unlock(); }).start();
執行結果(多次):
2018-03-18 11:03:34 INFO ConcurrentOrderlyDemo:100 - 通過Lock保證有序性:成功 A : x = x + 1 A : x = x - 1 B : x = x * 2 B : x = x / 2
通過多次執行,發現執行結果一致,所以可以確定Lock介面能夠保證程式碼的有序性。
4.總結
經驗證,以下兩種措施,可以保證Java程式碼在執行時的有序性:
- synchronized關鍵字
- Lock介面
併發三特性總結
特性 | volatile關鍵字 | synchronized關鍵字 | Lock介面 | Atomic變數 |
原子性 | 無法保障 | 可以保障 | 可以保障 | 可以保障 |
可見性 | 可以保障 | 可以保障 | 可以保障 | 可以保障 |
有序性 | 一定程度保障 | 可以保障 | 可以保障 | 無法保障 |