現(xiàn)在共享經(jīng)濟比較火爆笤闯,直接點就是所有權(quán)和使用權(quán)的解耦堕阔,通過分時錯位使用,達到較高的資源利用程度望侈。
在第一節(jié)中我們問了幾個問題印蔬,其中第4個問題就是線程與資源的關(guān)系。我們前面說過脱衙,進程 = 資源 + 線程侥猬,也就是說,資源的隔離是在進程層面上的捐韩,沒有在線程層面做隔離(也有一些變量隔離在線程上退唠,但是大部分都是引用,這個無關(guān)大局)荤胁。因為進程是對應(yīng)應(yīng)用的瞧预,一個應(yīng)用內(nèi),資源在很大程度上有一定的關(guān)聯(lián)性仅政,如果再在這個層面上做冗余垢油,一方面在空間上,會有很大浪費圆丹,另一方面滩愁,在資源狀態(tài)的同步上,勢必也夠運行時整理的辫封。所以硝枉,自己的程序,還是交給自己去處理吧倦微。
資源從哪里來的妻味?
資源是在任務(wù)執(zhí)行過程中,按需創(chuàng)建出來的欣福。
資源放在哪里责球?
資源存放在進程的地址空間內(nèi),按照不同的內(nèi)存模型定義,放置在不同的位置棕诵,比如Java內(nèi)部模型中對于方法區(qū)裁良、堆、棧校套、本地方法區(qū)等定義价脾。
資源如何使用?
在線程執(zhí)行到一定位置笛匙,需要相應(yīng)資源時侨把,就依據(jù)資源的引用(地址)去獲取資源。
如果同時幾個線程都需要獲取一個資源怎么辦(資源共享問題)妹孙?
這個時候就需要協(xié)調(diào)一下秋柄,不能你用我也用,這樣會導(dǎo)致資源的狀態(tài)不可預(yù)測蠢正,直接違反的程序的可預(yù)測性的基本要求骇笔,所以。需要一定的機制來保證有規(guī)矩嚣崭。這個規(guī)矩就是鎖笨触。
我們知道,出現(xiàn)競態(tài)條件的原因是資源共享雹舀,那么落腳點在資源本身芦劣。其實處理做法比較簡單。就是在資源上说榆,統(tǒng)一都配置上二元信號量虚吟,外加一個等待隊列。其實很容易理解签财。信號量就是一個數(shù)值串慰,可以理解為信號的強度。二元信號量就是只有0和1兩個強度唱蒸。當(dāng)多個線程(擁有軀殼的靈魂邦鲫,擁有CPU的線程)來請求一個資源時,先訪問信號量油宜,如果此時信號量為1,那么就將這個信號量減1怜姿,變?yōu)?慎冤。下一個過來的線程檢查到信號量為0,表示不可用沧卢,就將自己加入到等待隊列蚁堤。前面獲得資源的線程執(zhí)行完之后,將信號量加1但狭,并告訴等到隊列的線程披诗,你們可以來用這個資源了撬即。
所以,在java中呈队,wait()方法剥槐,notify()方法,notifyAll()方法是Object的方法宪摧,而非Thread的方法粒竖,因為調(diào)用wait()時,是讓CPU往資源對象的等待列表里面加一個等待線程(PCB)几于,當(dāng)其他線程調(diào)用該資源對象的notify()或者notiryAll()時蕊苗,也是告訴等待隊列中的線程(PCB)可以來嘗試獲取這個資源了。當(dāng)然沿彭,這個也都不是真實的朽砰,真實的情況還是操作系統(tǒng)將這些個等待線程的PCB對應(yīng)的狀態(tài)由waiting修改為Ready,然后等著操作系統(tǒng)給分配上CPU的時間喉刘。
這其中有一個比較隱藏的點瞧柔,就是如果線程A打算獲取資源,但是沒有獲取到資源的鎖饱搏,這個時候線程A還在CPU上非剃,雖然沒獲取到資源,但是還能活動(現(xiàn)在靈魂還裝在軀殼里)推沸,不過它已經(jīng)不能再繼續(xù)往下干活了备绽,因為缺少必要的資源,接下來它就將自己的狀態(tài)修改為Blocked鬓催,阻塞肺素。所以,阻塞這個狀態(tài)是主動的宇驾,不是被阻塞倍靡。等到將自己變?yōu)樽枞麪顟B(tài)之后,它開始提前結(jié)束自己的時間片课舍,因為接下來占用時間片也沒意義了塌西,所以,這種方式還叫做協(xié)作筝尾。但是這個自我阻塞的線程捡需,是沒辦法自我喚醒的,因為這個狀態(tài)下筹淫,它始終無法獲取CPU站辉,就沒有行動的能力。所以,在一個獲取資源的線程在操作完畢之后饰剥,調(diào)用notify()或者notifyAll()時殊霞,通知操作系統(tǒng),從該資源的等待隊列選擇一個PCB修改為非Blocked汰蓉,這個時候绷蹲,其他線程就獲得了上CPU的可能,然后又開始了下一輪的循環(huán)古沥。
在進行協(xié)作的時候瘸右,一個線程調(diào)用wait()的前提是它具有CPU執(zhí)行時間片(有活動能力),并且持有這個資源岩齿,在調(diào)用之后太颤,它就釋放了這個資源,也就是將信號量加1了盹沈。但是有一個情況龄章,就是調(diào)用Sleep()函數(shù)時,這個時候乞封,當(dāng)前線程在具備行動能力的時候做裙,卻沒有釋放資源,只是自己啥也不干了肃晚,但是也不會釋放已有的資源锚贱。所以,Sleep()跟資源沒關(guān)系关串,它是Thread的方法拧廊。明白了嗎?
還有一個方法晋修,就是join()方法吧碾,它的作用就是讓一個線程可以等待另一個線程執(zhí)行完畢。歸根結(jié)底它是在內(nèi)部調(diào)用wait()方法墓卦,不過它是Thread的實例方法倦春,而不是Object的方法。當(dāng)主線程調(diào)用某個線程實例的join()方法時落剪,讓我們過一遍里面邏輯的過程:首先主調(diào)線程t1上CPU睁本,開始執(zhí)行,執(zhí)行到t2.join()時忠怖,發(fā)現(xiàn)需要等到t2執(zhí)行完畢呢堰。這個時候,t2可能正在運行脑又,也可能被阻塞暮胧,這個無所謂。執(zhí)行t1的CPU開始修改t2的等待列表(join()里面包裹著wait()方法)问麸,將t1加入到里面往衷,然后t1就自己的時間片交出,大喊一聲严卖,我下CPU了席舍,你們可以上了。等到t2執(zhí)行完畢哮笆,調(diào)用t2的notify()或者notiryAll()方法来颤,讓CPU將等待列表中的線程的狀態(tài)修改為Ready,這樣就等于喚醒了等待它的線程稠肘,這個時候t2是執(zhí)行完畢了福铅,那么相當(dāng)于t2執(zhí)行完畢了,t1就開始執(zhí)行项阴,完成了這種首尾相接滑黔。