☕️【Java技術之旅】【AbstractQueuedSynchronizer】教你自定義實現自己的同步器

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"前提概要","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"之前的文章中會涉及到了相關AQS的原理和相關源碼的分析,所謂實踐是檢驗真理的唯一標準!接下來就讓我們活化一下AQS技術,主要針對於自己動手實現一個AQS同步器。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"定義MyLock實現Lock","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Doug Lea大神在JDK1.5編寫了一個Lock接口,裏面定義了實現一個鎖的基本方法,我們只需編寫一個MyLock類實現這個接口就好。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"class MyLock implements Lock {\n /**\n * 加鎖。如果不成功則進入等待隊列\n */\n @Override\n public void lock() {}\n /**\n * 加鎖(可被interrupt)\n */\n @Override\n public void lockInterruptibly() throws InterruptedException {}\n /**\n * 嘗試加鎖\n */\n @Override\n public boolean tryLock() {}\n /**\n * 加鎖 帶超時的\n */\n @Override\n public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {}\n /**\n * 釋放鎖\n */\n @Override\n public void unlock() {}\n /**\n * 返回一個條件變量(不在本案例談論)\n */\n @Override\n public Condition newCondition() {}\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"定義好MyLock後,接下來就是實現各個方法的邏輯,達到真正的用於線程間sync互斥的需求。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"自定義一個MySync繼承自AQS","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來我們需要自定義一個繼承自AQS的MySync。實現自定義的MySync前,先了解AQS內部的一些基本概念。在AQS中主要的一些成員屬性如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/23/23c15489688308fb9c8e865137b725c0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"state:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用於標記資源狀態,如果爲0表示資源沒有被佔用,可以加鎖成功。如果大於0表示資源已經被佔用,然後根據自己的定義去實現是否允許對共享資源進行操作","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如:ReentrantLock的實現方式是當state大於0,那麼表示已經有線程獲得鎖了,我們都知道ReentrantLock是可重入的,其原理就是當有線程次進入同一個lock標記的臨界區時。先判斷這個線程是否是獲得鎖的那個線程,如果是,state會+1,此時state會等於2。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當unlock時,會一層一層的減1,直到state等於0則表示完全釋放鎖成功。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"head、tail:用於存放獲得鎖失敗的線程。在AQS中,每一個線程會被封裝成一個Node節點,這些節點如果獲得鎖資源失敗會鏈在head、tail中,成爲一個雙向鏈表結構。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"exclusiveOwnerThread","attrs":{}},{"type":"text","text":":","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"用於存放當前獲得鎖的線程,正如在state說明的那樣。ReentrantLock判斷可重入的條件就是用這個exclusiveOwnerThread線程跟申請獲得鎖的線程做比較,如果是同一個線程,則state+1,並重入加鎖成功","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"知道這些概念後我們就可以自定義一個AQS:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public final class MySync extends AbstractQueuedSynchronizer {\n /**\n * 嘗試加鎖\n */\n @Override\n protected boolean tryAcquire(int arg) {\n if (compareAndSetState(0, 1)) {\n // 修改state狀態成功後設置當前線程爲佔有鎖資源線程\n setExclusiveOwnerThread(Thread.currentThread());\n return true;\n }\n return false;\n }\n /**\n * 釋放鎖\n */\n @Override\n protected boolean tryRelease(int arg) {\n setExclusiveOwnerThread(null);\n // state有volatile修飾,爲了保證解鎖後其他的一些變量對其他線程可見,把setExclusiveOwnerThread(null)放到上面 happens-before中定義的 volatile規則\n setState(0);\n return true;\n }\n /**\n * 判斷是否是獨佔鎖\n */\n @Override\n protected boolean isHeldExclusively() {\n return getState() == 1;\n }\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"將MySync組合進MyLock","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後一步就是將第一步中的所有方法邏輯完成","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"class MyLock implements Lock {\n\n // 組合自定義sync器\n private MySync sync = new MySync();\n\n /**\n * 加鎖。如果不成功則進入等待隊列\n */\n public void lock() {\n sync.acquire(1);\n }\n /**\n * 加鎖(可被interrupt)\n */\n public void lockInterruptibly() throws InterruptedException {\n sync.acquireInterruptibly(1);\n }\n /**\n * 嘗試加鎖\n */\n public boolean tryLock() {\n return sync.tryAcquire(1);\n }\n /**\n * 加鎖 帶超時的\n */\n public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {\n return sync.tryAcquireNanos(1, unit.toMillis(time));\n }\n /**\n * 釋放鎖\n */\n public void unlock() {\n sync.release(0);\n }\n /**\n * 返回一個條件變量(不在本案例談論)\n */\n @Override\n public Condition newCondition() {\n return null;\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完成整個MyLock的邏輯後,發現在lock()、unlock()中調用的自定義sync的方法tryAcquire()和tryRelease()方法。我們就以在lock()方法中調用acquire()方法說明模板設計模式在AQS中的應用。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點進.acquire()方法後,發現改該方法是來自AbstractQueuedSynchronizer中:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4a/4aa6d2a4de57abc59c114214485bfbb3.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這裏面可以看到tryAcquire方法,繼續點進去看看tryAcquire(),發現該方法是一個必須被重寫的方法,否則拋出一個運行時異常。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"模板方法設計模式在這裏得以體現,再回到我們第二部中自定義的MySync中,就是重寫了AQS中的tryAcquire()方法。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c6/c66b7c14a948aeaefdbfbc8e8ed1249a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此整個自定義加鎖的流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用MyLock的lock(),lock()方法調用AQS的acquire()方法","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在acquire()方法中調用了tryAcquire()方法進行加鎖","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而tryAcquire()方法在AQS中是一個必須讓子類自定義重寫的方法,否則會拋出一個異常","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此調用tryAcquire()時實際上是調用了我們自定義的MySync類中tryAcquire()方法","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AQS作爲Java併發體系下的關鍵類,在各種併發工具中都有它的身影,如ReentrantLock、Semaphore等。這些併發工具用於控制sync互斥的手段都是採用AQS,外加Cas機制。AQS採用了模板方法設計模式讓子類們自定義sync互斥的條件,比如本案例中MySync類重寫了tryAcquire方法。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面實現一個自定義的sync:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class SelfSynchronizer {\n\n private final Sync sync = new Sync();\n\n public void lock() {\n sync.acquire(1);\n }\n\n public boolean tryLock() {\n return sync.tryAcquire(1);\n }\n\n public boolean unLock() {\n return sync.release(1);\n }\n\n static class Sync extends AbstractQueuedSynchronizer {\n //是否處於佔用狀態\n @Override\n protected boolean isHeldExclusively() {\n return getState() == 1;\n }\n\n /**\n * 獲取sync資源\n * @param acquires\n * @return\n */\n @Override\n public boolean tryAcquire(int acquires) {\n if(compareAndSetState(0, 1)) {\n setExclusiveOwnerThread(Thread.currentThread());\n return true;\n }\n //這裏沒有考慮可重入鎖\n /*else if (Thread.currentThread() == getExclusiveOwnerThread()) {\n int nextc = c + acquires;\n if (nextc < 0) // overflow\n throw new Error(\"Maximum lock count exceeded\");\n setState(nextc);\n return true;\n }*/\n return false;\n }\n\n /**\n * 釋放sync資源\n * @param releases\n * @return\n */\n @Override\n protected boolean tryRelease(int releases) {\n int c = getState() - releases;\n boolean free = false;\n if (c == 0) {\n free = true;\n }\n setState(c);\n return free;\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ReentrantLock源碼和上面自定義的sync很相似,測試下該sync,i++在多線程下執行情況:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class TestSelfSynchronizer {\n private static int a = 0;\n private static int b = 0;\n private static SelfSynchronizer selfSynchronizer = new SelfSynchronizer();\n private static ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 50, 1, TimeUnit.SECONDS,\n new LinkedBlockingQueue());\n private static ExecutorService ec = Executors.newFixedThreadPool(20);\n public static void main(String[] args) throws InterruptedException {\n for (int i = 0; i < 20 ; i++) {\n executor.submit(new Task());\n }\n for (int j = 0; j < 20 ; j++) {\n ec.submit(new TaskSync());\n }\n Thread.sleep(10000);\n System.out.println(\"a的值:\"+ a);\n System.out.println(\"b的值\" + b);\n executor.shutdown();\n ec.shutdown();\n }\n static class Task implements Runnable {\n @Override\n public void run() {\n for(int i=0;i<10000;i++) {\n a++;\n }\n }\n }\n static class TaskSync implements Runnable {\n @Override\n public void run() {\n for (int i = 0; i < 10000; i++) {\n //使用sync器加鎖\n selfSynchronizer.lock();\n b++;\n selfSynchronizer.unLock();\n }\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開啓兩個線程池,對int型變量自增10000次,如果不加sync器,最後值小於200000,使用了自定義sync器則最後值正常等於200000,這是因爲每次自增操作加鎖","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章