1、背景java
在看併發包源碼的時候看見過LockSupport,今天恰巧看到LockSupport字眼,因而看下jdk1.7中的源碼結構。想着它應該是運用多線程的鎖工具的,到底彷佛怎麼實現的呢?
多線程
2、使用併發
因而本身寫個demo對比下synchronized框架
場景:main線程中建立一個線程A,想讓threadA 循環完畢的時候先阻塞,而後再main線程中釋放。ide
1.控制多線程併發:工具
1 public static void main(String[] args) { 2 Object obj = new Object(); 3 generalSync(obj); 4 obj.notifyAll(); 5 System.out.println("主線程執行完畢"); 6 } 7 8 public static void generalSync(final Object obj) { 9 Runnable runnable = new Runnable() { 10 @Override 11 public void run() { 12 for (int i = 0; i < 10000; i++) { 13 System.out.println(i); 14 } 15 System.out.println("循環完畢"); 16 try { 17 obj.wait(); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println("線程A執行完畢"); 22 } 23 }; 24 Thread threadA = new Thread(runnable); 25 threadA.start(); 26 }
上面這個最終結果是什麼呢情?會以異常結束:java.lang.IllegalMonitorStateException, 產生的緣由是Object的wait,notify,notifyAll方法必須在同步塊中執行spa
2.使用synchronized但主線程接觸阻塞在threadA阻塞執行以前線程
1 public static void main(String[] args) { 2 Object obj = new Object(); 3 generalSync(obj);
// Thread.sleep(1000); 4 synchronized (obj) { 5 obj.notifyAll(); 6 } 7 System.out.println("主線程執行完畢"); 8 } 9 10 public static void generalSync(final Object obj) { 11 Runnable runnable = new Runnable() { 12 @Override 13 public void run() { 14 for (int i = 0; i < 10000; i++) { 15 System.out.println(i); 16 } 17 System.out.println("循環完畢"); 18 try { 19 synchronized (obj) { 20 obj.wait(); 21 } 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 System.out.println("線程A執行完畢"); 26 } 27 }; 28 Thread threadA = new Thread(runnable); 29 threadA.start(); 30 } 31
上面會致使一直阻塞,由於主線程在第3行開啓一個threadA後,就往下執行4~7代碼,而threadA須要循環所用的時間更久。所以會先調用obj.notifyAll(),而後再obj.wait()致使調用順序錯誤一直阻塞。想要達到咱們預期的目的,根據threadA時間,能夠在第4行添加Thread.sleep(1000)使主線程中的obj.notifyAll()晚於obj.wait()執行。code
3.使用LockSupport來實現同步對象
1 public static void main(String[] args) { 2 Thread threadA = generalSync(); 3 LockSupport.unpark(threadA); 4 System.out.println("主線程執行完畢"); 5 } 6 7 public static Thread generalSync() { 8 Runnable runnable = new Runnable() { 9 @Override 10 public void run() { 11 for (int i = 0; i < 10000; i++) { 12 System.out.println(i); 13 } 14 System.out.println("循環完畢"); 15 LockSupport.park(); 16 System.out.println("線程A執行完畢"); 17 } 18 }; 19 Thread threadA = new Thread(runnable); 20 threadA.start(); 21 return threadA; 22 }
經過LockSupport無需關於阻塞和釋放的調用前後問題,僅僅經過park/unpark便可阻塞或釋放。park/unpark模型真正解耦了線程之間的同步,線程之間再也不須要一個Object或者其它變量來存儲狀態,再也不須要關心對方的狀態。
3、閱讀源碼
經過類註釋介紹,LockSupport是JDK中比較底層的類,用來建立鎖和其餘同步工具類的基本線程阻塞原語。java鎖和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是經過調用 LockSupport .park()和 LockSupport .unpark()實現線程的阻塞和喚醒 的,下面重點關注着2個方法。
1 public class LockSupport { 2 private LockSupport() {} // Cannot be instantiated. 3 4 // Hotspot implementation via intrinsics API 5 private static final Unsafe unsafe = Unsafe.getUnsafe(); 6 private static final long parkBlockerOffset;
1.根據上面的類定義,內部構造方法私有化,外部類沒法實例,做爲工具類使用的意圖。類中實例化了一個Unsafe這個可直接操做內存的本地API
1 public static void park(Object blocker) { 2 Thread t = Thread.currentThread(); 3 setBlocker(t, blocker); 4 unsafe.park(false, 0L); 5 setBlocker(t, null); 6 }
2.阻塞是經過park方法,第2行獲取當前線程;第3行執行setBlocker(Thread t, Object arg),setBlocker從名字感受設置阻塞的地方;第4行調用Unsafe中public native void park(boolean paramBoolean, long paramLong)產生阻塞;第5行調用setBlocker(Thread t, Object arg),可是Object參數是null,此處應該是去除阻塞點。
1 public class LockSupport { 2 private LockSupport() {} // Cannot be instantiated. 3 4 // Hotspot implementation via intrinsics API 5 private static final Unsafe unsafe = Unsafe.getUnsafe(); 6 private static final long parkBlockerOffset; 7 8 static { 9 try { 10 parkBlockerOffset = unsafe.objectFieldOffset 11 (java.lang.Thread.class.getDeclaredField("parkBlocker")); 12 } catch (Exception ex) { throw new Error(ex); } 13 } 14 15 private static void setBlocker(Thread t, Object arg) { 16 // Even though volatile, hotspot doesn't need a write barrier here. 17 unsafe.putObject(t, parkBlockerOffset, arg); 18 }
3.setBlocker是調用Unsafe中public native void putObject(Object paramObject1, long paramLong, Object paramObject2)方法,這個long類型paramLong傳的是Thread在內存中的偏移量。經過8~13能夠看出在靜態代碼塊中經過Unsafe中public native long objectFieldOffset(Field paramField)來獲得內存中位置(以前原子操做類也是經過此方法來獲取偏移量)這個偏移量屬性名稱是"parkBlocker",類型從第11行能夠知道parkBlocker是java.lang.Thread類中的一個屬性,
public class Thread implements Runnable { /* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); } ... ... /** * The argument supplied to the current call to * java.util.concurrent.locks.LockSupport.park. * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker * Accessed using java.util.concurrent.locks.LockSupport.getBlocker */ volatile Object parkBlocker;
這個parkBlocker應該是Thread類變量。首先LockSupport 是構造方法私有化,相似一個工具類,並且parkBlockerOffset是static類型的,因此即便在多個場景下的多線程環境,不一樣的多個Thread調用setBlocker方法都只是針對Thread的類變量進行賦值(類變量只有一個)因此是多對一的關係。而且經過getBlocker源碼註釋能夠看出
1 /** 2 * Returns the blocker object supplied to the most recent 3 * invocation of a park method that has not yet unblocked, or null 4 * if not blocked. The value returned is just a momentary 5 * snapshot -- the thread may have since unblocked or blocked on a 6 * different blocker object. 7 * 8 * @param t the thread 9 * @return the blocker 10 * @throws NullPointerException if argument is null 11 * @since 1.6 12 */ 13 public static Object getBlocker(Thread t) { 14 if (t == null) 15 throw new NullPointerException(); 16 return unsafe.getObjectVolatile(t, parkBlockerOffset); 17 }
從註釋看出「返回的是最近被阻塞的對象,相似一個瞬間的快照」,那麼我理解Thread中Object parkBlocker屬性可能會被多個線程賦值。這個屬性跟併發阻塞並沒有關係,只是起到記錄阻塞對象的做用。
至於Unsafe類中native方法,就沒有去追究。看其餘博客理解到的是:在hotspot裏每一個java線程都有一個Parker實例,Parker裏使用了一個無鎖的隊列在分配釋放Parker實例。Parker裏面有個變量,volatile int _counter 至關於許可,二元信號量(相似0和1)
當調用park時,先嚐試直接可否直接拿到「許可」(即_counter>0時)若是成功,則把_counter設置爲0,並返回;若是不成功,則構造一個ThreadBlockInVM,而後檢查_counter是否是>0,若是是,則把_counter設置爲0
當unpark時,則簡單多了,直接設置_counter爲1,若是_counter以前的值是0,則還要調用喚醒在park中等待的線程: