要學(xué)習(xí)多線程一些基本的同步類也是不得不學(xué)習(xí)的,這里主要講一點基本的概念與使用。
阻塞隊列
阻塞隊列提供可阻塞的put和take方法,支持定時的offer和poll方法,如果隊列已經(jīng)滿了,那么put方法將阻塞直到有空間可用;如果隊列為空,那么take方法將會阻塞直到有元素可用;同時隊列可以是有界也可以是無界的,無界隊列永遠都不會充滿,因此無界隊列的put方法永遠不會阻塞;
JAVA中的阻塞隊列主要有以實現(xiàn)BlockingQueue接口的幾個類,其中LinkedBlockingQueue、ArrayBlockingQueue對應(yīng)LinkedList與ArrayList,他們是先進先出隊列(FIFO);阻塞隊列能夠?qū)崿F(xiàn)生產(chǎn)者與消費者的關(guān)系,生產(chǎn)者往隊列中put數(shù)據(jù),而消費者往take中拉取數(shù)據(jù),進而實現(xiàn)異步操作。曾經(jīng)的生產(chǎn)中的例子簡化后如下圖:

隊列還有一些實現(xiàn)比如:
PriorityBlockingQueue隊列是優(yōu)先級隊列,根據(jù)元素的比較來控制順序;
SynchronousQueue隊列維護的不是元素而是線程,這些線程在等待把元素加入或者移除隊列,就好像餐館出菜,前面的隊列都是把菜炒好了放到放菜的地方,服務(wù)員一個一個拿走,而SynchronousQueue則沒有放菜的地方,廚師炒好菜直接給服務(wù)員端走,所以SynchronousQueue要求消費者足夠多,并且總是有至少一個消費者在等待才適合用。
同步工具類
閉鎖
閉鎖:可以延遲線程的進度直到其到達終止狀態(tài)。閉鎖相當于一個閘門,在閉鎖到達結(jié)束狀態(tài)之前,這個閘門一直是關(guān)閉的,任何線程都不能通過當?shù)竭_結(jié)束狀態(tài)時,閘門允許所有線程通過,閉鎖到達結(jié)束狀態(tài)后不會再改變狀態(tài)閉鎖。
閉鎖主要作用是可以用來確保一些活動直到其他活動都完成后才繼續(xù)執(zhí)行。比如一個初始化需要等另一個初始化,比如所有玩家都準備才開始。
CountDownLatch是一種閉鎖實現(xiàn),它初始化一個正數(shù),countDown方法使數(shù)字遞減,await方法使線程等待計數(shù)器到0,否則阻塞,示例如下圖:

這個方法主要用來統(tǒng)計一個任務(wù)在多個線程同步執(zhí)行下消耗時間,利用兩個閉鎖實現(xiàn)了線程先一起準備好,然后放開開始閉鎖,所有線程一起執(zhí)行,每個線程最后都會countDown一下結(jié)束閉鎖,當所有線程執(zhí)行完成結(jié)束閉鎖也就方法,這樣就可以實現(xiàn)統(tǒng)計所有線程一起執(zhí)行消耗的時間。
FutureTask
FutrueTask是一個實現(xiàn)了Runnable的類,并且它可以獲取到線程的執(zhí)行結(jié)果,get方法執(zhí)行取決于任務(wù)的狀態(tài),如果任務(wù)已經(jīng)完成,那么get會立即返回結(jié)果,否則get將
阻塞直到任務(wù)進入完成狀態(tài),可以利用它的這個功能來實現(xiàn)閉鎖。
它還可以用來異步提前計算一些比較耗性能的值,比如一個操作比較耗性能那么可以讓它先執(zhí)行,等到需要計算的結(jié)果的時候在get。
信號量
計數(shù)信號量(Counting Semaphore)用來控制同時訪問某個特定資源的操作數(shù)量,或者同時執(zhí)行某個指定操作的數(shù)量。
實現(xiàn)原理是:Semaphore管理著一組虛擬的許可,執(zhí)行操作前必須先acquire獲得許可,這個許可就減一,如果許可被減到0,線程再來獲取許可就阻塞等待其他線程在操作完成后release釋放許可;
比如初始化了一定數(shù)量的數(shù)據(jù)庫線程池,每個線程獲取線程池時先acquire,如果獲取成功說明有線程繼續(xù)執(zhí)行,如果沒有線程了acquire方法會阻塞等待有多余線程。
還可以把計數(shù)信號量的數(shù)量設(shè)置為1,那么只要有一個線程acquire成功,其他線程就只能等待直到線程release,這樣就實現(xiàn)了一個互斥鎖。
柵欄
柵欄類似于閉鎖,他也是阻塞一組線程直到某個事件發(fā)生,區(qū)別在于柵欄是等待所有線程到達指定,而閉鎖是等待一個事件,閉鎖是一次性的不會重置。閉鎖是等待其他線程執(zhí)行完成等待線程才開始執(zhí)行,而柵欄是讓每個線程都等待,直到所有線程都是等待狀態(tài),那么所有線程一起繼續(xù)執(zhí)行。
可能有點不好理解,通過下面一個例子和閉鎖例子進行對比,示例如下圖:

不管執(zhí)行多少次,一定是所有的"開始"打印完成后才會打印"結(jié)束",并且如果線程的數(shù)量沒有初始化柵欄的值多,所有線程都會處于阻塞狀態(tài)。
總結(jié)
今天總結(jié)了一點阻塞隊列的知識和應(yīng)用,平時一些消耗性能而創(chuàng)建線程去異步執(zhí)行可能會造成線程太多的情況,可以簡單的通過阻塞隊列來實現(xiàn)生產(chǎn)者消費者的方式來解決問題。
還總結(jié)了一點同步工具類的知識,主要是理解這些基本概念和使用場景,閉鎖通過觸發(fā)countDown來減少計數(shù)器,當計數(shù)器減到0,await方法放行。信號量則是acquire方法去獲取release方法去釋放,柵欄則是先運行到await地方的線程先等待,等到所有線程都到了再一起執(zhí)行,尤其要理解閉鎖和柵欄的區(qū)別。
閉鎖用來確保一些活動執(zhí)行完成才執(zhí)行一些操作,信號量用來保證某些操作的數(shù)量,柵欄用來等待所有操作一起到達指定點再一起繼續(xù)執(zhí)行。
Java程序員日常學(xué)習(xí)筆記,如理解有誤歡迎各位交流討論!