日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

LockSupport的源码实现原理以及应用

發布時間:2023/12/10 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LockSupport的源码实现原理以及应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、為什么使用LockSupport類?

如果只是LockSupport在使用起來比Object的wait/notify簡單,

那還真沒必要專門講解下LockSupport。最主要的是靈活性。

上邊的例子代碼中,主線程調用了Thread.sleep(1000)方法來等待線程A計算完成進入wait狀態。如果去掉Thread.sleep()調用,代碼如下:

note:這個場景需要注意一下 防止在業務場景中出現這種bug。

public class TestObjWait {public static void main(String[] args)throws Exception {final Object obj = new Object();Thread A = new Thread(new Runnable() {@Overridepublic void run() {int sum = 0;for(int i=0;i<10;i++){sum+=i;}try {synchronized (obj){obj.wait();}}catch (Exception e){e.printStackTrace();}System.out.println(sum);}});A.start(); //睡眠一秒鐘,保證線程A已經計算完成,阻塞在wait方法//Thread.sleep(1000);synchronized (obj){obj.notify();}} }

?

多運行幾次上邊的代碼,有的時候能夠正常打印結果并退出程序,但有的時候線程無法打印結果阻塞住了。原因就在于:主線程調用完notify后,線程A才進入wait方法,

導致線程A一直阻塞住。由于線程A不是后臺線程,所以整個程序無法退出。

那如果換做LockSupport呢?LockSupport就支持主線程先調用unpark后,線程A再調用park而不被阻塞嗎?是的,沒錯。代碼如下

public class TestObjWait {public static void main(String[] args)throws Exception {final Object obj = new Object();Thread A = new Thread(new Runnable() {@Overridepublic void run() {int sum = 0;for(int i=0;i<10;i++){sum+=i;}LockSupport.park();System.out.println(sum);}});A.start();//睡眠一秒鐘,保證線程A已經計算完成,阻塞在wait方法//Thread.sleep(1000);LockSupport.unpark(A);} }

?

不管你執行多少次,這段代碼都能正常打印結果并退出。這就是LockSupport最大的靈活所在。

?

總結一下,LockSupport比Object的wait/notify有兩大優勢

①LockSupport不需要在同步代碼塊里 。所以線程間也不需要維護一個共享的同步對象了,實現了線程間的解耦。

②unpark函數可以先于park調用,所以不需要擔心線程間的執行的先后順序。

?

三、應用廣泛

LockSupport在Java的工具類用應用很廣泛,咱們這里找幾個例子感受感受。

以Java里最常用的類ThreadPoolExecutor為例。先看如下代碼:

public class TestObjWait {public static void main(String[] args)throws Exception {ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(1000);ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,5,1000, TimeUnit.SECONDS,queue);Future<String> future = poolExecutor.submit(new Callable<String>() {@Overridepublic String call() throws Exception {TimeUnit.SECONDS.sleep(5);return "hello";}});String result = future.get();System.out.println(result);} }

?代碼中我們向線程池中扔了一個任務,然后調用Future的get方法,同步阻塞等待線程池的執行結果。

這里就要問了:get方法是如何組塞住當前線程?線程池執行完任務后又是如何喚醒線程的呢?

咱們跟著源碼一步步分析,先看線程池的submit方法的實現:

在submit方法里,線程池將我們提交的基于Callable實現的任務,封裝為基于RunnableFuture實現的任務,然后將任務提交到線程池執行,并向當前線程返回RunnableFutrue。

進入newTaskFor方法,就一句話:return new FutureTask<T>(callable);

所以,咱們主線程調用future的get方法就是FutureTask的get方法,線程池執行的任務對象也是FutureTask的實例。

接下來看看FutureTask的get方法的實現:

比較簡單,就是判斷下當前任務是否執行完畢,如果執行完畢直接返回任務結果,否則進入awaitDone方法阻塞等待。

awaitDone方法里,首先會用到上節講到的cas操作,將線程封裝為WaitNode,保持下來,以供后續喚醒線程時用。再就是調用了LockSupport的park/parkNanos組塞住當前線程。

?

上邊已經說完了阻塞等待任務結果的邏輯,接下來再看看線程池執行完任務,喚醒等待線程的邏輯實現。

前邊說了,咱們提交的基于Callable實現的任務,已經被封裝為FutureTask任務提交給了線程池執行,任務的執行就是FutureTask的run方法執行。如下是FutureTask的run方法:

c.call()就是執行我們提交的任務,任務執行完后調用了set方法,進入set方法發現set方法調用了finishCompletion方法,想必喚醒線程的工作就在這里邊了,看看代碼實現吧:

沒錯就在這里邊,先是通過cas操作將所有等待的線程拿出來,然后便使用LockSupport的unpark喚醒每個線程。

?

在使用線程池的過程中,不知道你有沒有這么一個疑問:線程池里沒有任務時,線程池里的線程在干嘛呢?

答案是 線程會調用隊列的take方法阻塞等待新任務。那隊列的take方法是不是也跟Future的get方法實現一樣呢?

以ArrayBlockingQueue為例,take方法實現如下:

與想象的有點出入,他是使用了Lock的Condition的await方法實現線程阻塞。但當我們繼續追下去進入await方法,發現還是使用了LockSupport:

限于篇幅,jdk里的更多應用就不再追下去了。

?

四、LockSupport的實現

學習要知其然,還要知其所以然。接下來不妨看看LockSupport的實現。

進入LockSupport的park方法,可以發現它是調用了Unsafe的park方法,這是一個本地native方法,只能通過openjdk的源碼看看其本地實現了,可以看出底層的源碼是 C++實現的了;?

它調用了線程的Parker類型對象的park方法,如下是Parker類的定義:主要看的 私有成員? 構造函數 析構函數? 以及其 parker和 unparker 方法。

類中定義了一個int類型的_counter變量,咱們上文中講靈活性的那一節說,可以先執行unpark后執行park,就是通過這個變量實現,看park方法的實現代碼(由于方法比較長就不整體截圖了):

park方法會調用Atomic::xchg方法,這個方法會原子性的將_counter賦值為0,并返回賦值前的值。如果調用park方法前,_counter大于0,則說明之前調用過unpark方法,所以park方法直接返回。將_counterf 數值置為0;

接著往下看:

?

實際上Parker類用Posix的mutex,condition來實現的阻塞喚醒。如果對mutex和condition不熟,可以簡單理解為mutex就是Java里的synchronized,condition就是Object里的wait/notify操作。

park方法里調用pthread_mutex_trylock方法,就相當于Java線程進入Java的同步代碼塊,然后再次判斷_counter是否大于零,如果大于零則將_counter設置為零。最后調用pthread_mutex_unlock解鎖,

相當于Java執行完退出同步代碼塊。如果_counter不大于零,則繼續往下執行pthread_cond_wait方法,實現當前線程的阻塞。

?

最后再看看unpark方法的實現吧,這塊就簡單多了,直接上代碼:

?

圖中的1和4就相當于Java的進入synchronized和退出synchronized的加鎖解鎖操作,代碼2將_counter設置為1,

同時判斷先前_counter的值是否小于1,即這段代碼:if(s<1)? ,如果大于等于1,則就不會有線程被park,所以方法直接執行完畢,如果小于1 說明有線程被 park 了? 就會執行代碼3,來喚醒被阻塞的線程。

?

通過閱讀LockSupport的本地實現,我們不難發現這么個問題:多次調用unpark方法和調用一次unpark方法效果一樣,因為都是直接將_counter賦值為1,而不是加1。簡單說就是:線程A連續調用兩次LockSupport.unpark(B)方法喚醒線程B,然后線程B調用兩次LockSupport.park()方法, 線程B依舊會被阻塞。因為兩次unpark調用效果跟一次調用一樣,只能讓線程B的第一次調用park方法不被阻塞,第二次調用依舊會阻塞。

?

轉載于:https://www.cnblogs.com/gxyandwmm/p/9419129.html

總結

以上是生活随笔為你收集整理的LockSupport的源码实现原理以及应用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。