之前簡單的介紹了一些并發(fā)的基礎知識。還用實際代碼證實了常用集合類的不安全。另外也簡單說了下Concurrent中的常用的幾個輔助類吗货。今天這篇文章主要是簡單介紹下與Lock同等級的讀寫鎖還有隊列。
ReadWriteLock
讀寫鎖是個接口,我們先去手冊中簡單看下這個介紹:
其實這個讀不加鎖寫加鎖的設計在好多地方都有涉及鹃祖。比如數(shù)據(jù)庫讀寫分離。還有緩存是寫入的時候更新普舆。讀的話不更新恬口。種種行為都告訴我們讀和寫是不一樣的。其實這個也好理解沼侣,寫入操作會使得數(shù)據(jù)發(fā)生變化祖能。種種臟讀幻讀不可重復讀的原因都是寫入導致的!(準確的說是讀寫時機導致的蛾洛。但是本質(zhì)上還是因為寫改動數(shù)據(jù)了芯杀。不然重復讀百萬次也不會發(fā)生錯誤啊Q盘丁)所以讀寫鎖就是針對這一事實情況很人性化的一個設計:其實我們通俗理解揭厚,是不是就是寫的時候加鎖,讀的時候不用加鎖胺龉筛圆?它的設計特別簡單,只有兩個方法:
我們理解了讀寫鎖是什么接下來還要知道是怎么實現(xiàn)的椿浓,怎么使用它太援。
首先正常在多線程的寫入中,肯定會同時寫入多個扳碍,這個是很容易理解的提岔。附上demo代碼:
而讀寫鎖可以讓我們的寫入變得安全:
至于讀鎖,是可以同時執(zhí)行的笋敞,如下截圖:
基于以上讀寫鎖的特性碱蒙。寫鎖也被稱為獨占鎖。因為是同時只能有一個線程占有。而讀鎖也被稱為共享鎖赛惩“梗可以好多線程共享。
阻塞隊列
其實這個組賽隊列是可以分成兩個詞的:阻塞喷兼。隊列篮绰。
隊列:Queue,這個稍微理解點數(shù)據(jù)結構的都應該知道季惯,在中國排隊應該處處都在吠各,買票,排網(wǎng)紅店勉抓,食堂打飯等等走孽。一個顯著的特點就是先入先出。先排隊的人先辦業(yè)務這個很好理解琳状。
阻塞:這個概念不是很好理解磕瓷。但是我們換一個詞:阻止。就好理解多了念逞。比如去銀行辦業(yè)務困食,人家快下班了,就會阻止你排隊翎承,說別排了硕盹,今天辦不了了,明天早點來吧叨咖。你看你被阻止了是不是瘩例。排隊被阻止其實分兩種情況:一種是排隊的人夠了,不讓你排了甸各。還有一種是排隊的目標沒了(上面說的辦理業(yè)務的人下班了)不讓你排了垛贤。落實到Queue種,也是如下兩種情況:
- 寫入:隊列滿了趣倾,必須阻塞等待聘惦。
- 讀取:如果是隊列是空的儒恋,比如阻塞等待生產(chǎn)善绎。
上面說的兩種情況都是不得不阻塞。
下面我們?nèi)タ纯垂俜绞謨苑N對阻塞隊列的介紹:
其實這個類我們應該很熟悉诫尽。因為我們經(jīng)常用它的叔叔伯伯禀酱。附上類關系圖:
看了這個圖我們有沒有對這個阻塞隊列更加清晰一點:其實這個也算是集合家族的三代直系了。說它的本質(zhì)就是個集合其實也沒錯牧嫉。只不過這個集合不同于Set的無序唯一剂跟,List的散列或鏈表。而是一個先入先出的展現(xiàn)形式而已。而我們現(xiàn)在說的阻塞隊列是Queue的一個兒子浩聋,它的兄弟賊多观蜗。我簡單把我知道的標出來了臊恋,附上一個家族成員圖:
一般我們什么時候用阻塞隊列呢?多用于多線程并發(fā)處理抖仅,線程池之類的坊夫。
而隊列的使用其實和List,Set是差不多的撤卢。主要操作就是添加移除环凿。但是這里比較特殊,涉及到很多東西放吩。比如隊列滿了再添加是報錯啊智听,還是不報錯只返回false還是說把第一個自動出列(擠走)。同樣如果隊列空了再取值是怎么樣渡紫?返回null還是報錯到推?下面我們代碼種一個個嘗試一下。
-
拋出異常的方法
add當隊列滿了會報錯惕澎。
remove當隊列空了會報錯
獲取隊首元素而不出隊莉测,無元素報錯 -
有返回值不拋出異常的方法
offer添加隊列滿了返回false
poll移除隊列空了返回null
獲取隊首元素而不出隊。無元素返回null -
一直等待(一直阻塞)
添加的時候隊列滿了一直等待
拿取的時候隊列空了一直等待 -
等待超時(等一定時間就不等了)
我們可以看到offer方法是有個重載方法唧喉,帶有超時時間的
等待
poll同樣可以選擇等一段時間
poll的等待
以上4*2組存取捣卤。兩個獲取隊首元素而不出隊。一共十個api其實要根據(jù)實際情況使用八孝。都是比較常用的董朝,也比較好記。每個人都應該掌握干跛。
SynchronousQueue 同步隊列
這個其實比較好理解:就是沒有容量益涧。一次只能放一個元素。下面可以在代碼中看看:
這個demo就說明了SynchronousQueue 同步隊列的特性:只能存儲一個元素驯鳖。上一個出去了這個才能進去闲询。
線程池
池化技術:池化技術能夠減少資源對象的創(chuàng)建次數(shù),提高程序的性能浅辙,特別是在高并發(fā)下這種提高更加明顯扭弧。使用池化技術緩存的資源對象有如下共同特點:
- 對象創(chuàng)建時間長;
- 對象創(chuàng)建需要大量資源记舆;
- 對象創(chuàng)建后可被重復使用鸽捻。
我們在工作中:線程池,連接池,內(nèi)存池御蒲,對象池都是如此衣赶。
而線程池的好處:
- 降低資源消耗
- 提高響應速度
- 方便管理
下面我們詳細的講一下線程池。
線程池三大方法:
其實說到三大方法有一個工具類繞不過去了厚满,我們可以去官方手冊上看一下府瞄。叫做Executors.下面是官方手冊中的介紹:
其實這個類就好像Collections于集合。這個類也是一個單純的工具類碘箍。主要是可以創(chuàng)建一些默認的線程池遵馆。
而常用的三大方法也在這里,下面代碼一個個說明:
單例線程 Executors.newSingleThreadExecutor();
指定大小的線程池 Executors.newFixedThreadPool(n);(n是指定的線程數(shù))
創(chuàng)建一個大小可伸縮的線程池 newCachedThreadPool
其實關于這個要說一下丰榴,雖然是大小可伸縮货邓。但是也不能無限大啊。哪怕線程池受得了你這個cpu也受不了吧四濒。我們點進去看下這個方法的源碼:
如果到這你還沒才出來這個Integer.MAX_VALUE是啥换况,我們繼續(xù)往下走:
說真的這個方法不要就這么用,建議還是設置一個合理的數(shù)值盗蟆。畢竟一大波并發(fā)服務器都得沖廢了戈二。
其實我們可以挨個方法點進去看一波源碼:
那個可伸縮的上面已經(jīng)看過了,是不是三個方法最終落實到的方法都是ThreadPoolExecutor澳飞挽拂?
而且本質(zhì)上也就是這個方法的參數(shù)不同。下面我們?nèi)シ治龇治鲞@個方法和參數(shù)骨饿。
線程池七個參數(shù)
線程池的三大方法已經(jīng)說過了亏栈,下面說七大參數(shù):
下面一個個參數(shù)說一下:
- int corePoolSize 核心線程數(shù)
- int maximumPoolSize 最大線程數(shù)
- long keepAliveTime 等待時間
- TimeUnit unit 等待時間的時間單位
- BlockingQueue<Runnable> workQueue 一個阻塞隊列宏赘。
- Executors.defaultThreadFactory() 默認的線程工廠
- defaultHandler 拒絕策略
這塊用現(xiàn)實中的例子簡單介紹下:如果說線程池就是銀行绒北。那么核心線程數(shù)就是銀行的常年開的窗口。而最大線程數(shù)就是銀行有的窗口(一般銀行不會把所有窗口都開著察署,尤其是人少的時候)闷游。而阻塞隊列就是除了正在辦理業(yè)務的人以外等待的人。當?shù)却娜说竭_一定程度(隊列滿了)贴汪,銀行會臨時開幾個窗口來辦理業(yè)務的脐往。但是當人少了以后,臨時窗口沒人辦理業(yè)務扳埂。在等待一段時間后就會繼續(xù)關閉业簿。等著下次人多了再開。而拒絕策略是如果所有的窗口都開了阳懂,而且等待區(qū)也滿了梅尤,這個時候還有人要進來肯定是進不來了柜思,是讓在門口等著還是給攆回去,這個措施就是拒絕策略巷燥!
線程池四種拒絕策略
四種拒絕策略(new ThreadPoolExecutor.出來的):
- AbortPolicy 隊列滿了還有人進來,會拋出異常(默認)
- DiscardPolicy 隊列滿了會丟掉任務,不會拋出異常
- DiscardOldestPolicy 隊列滿了會嘗試和最早的競爭,也不會拋出異常.
- CallerRunsPolicy 哪來的回哪去.沒有異常,沒有行為.
線程池調(diào)優(yōu)
線程池的大小到底如何設置?
- CPU密集型:幾核就是幾線程赡盘。可以保持CPU的效率最高缰揪。(ps:這里不要只看當前開發(fā)環(huán)境的電腦陨享,因為服務器會性能比開發(fā)環(huán)境好的多,建議用代碼獲取邀跃。)
//此方法打印可用處理器的虛擬機的最大數(shù)量
System.out.println(Runtime.getRuntime().availableProcessors());
- IO密集型:程序中有很多耗io的線程(因為io本身比較慢)要設置大于IO任務的線程霉咨。
本篇筆記就記到這里蛙紫,如果稍微幫到你了記得點個喜歡點個關注拍屑,也祝大家工作順順利利!