鄭州微盟網(wǎng)站建設(shè)公司青島網(wǎng)站seo
文章目錄
- 一、背景
- 二、什么是JUC?
- 三、JUC框架結(jié)構(gòu)
- 四、JUC框架概述
- 五、JUC中常用類匯總
- 六、相關(guān)名詞
- 進(jìn)程和線程
- 進(jìn)程
- 線程
- 創(chuàng)建線程的幾種常見的方式
- 并發(fā)和并行
- 用戶線程和守護(hù)線程
- 七、synchronized 作用范圍:
- 八、Lock鎖(重點(diǎn))
- 什么是 Lock
- 鎖類型
- Lock接口
- lock()、unlock()
- newCondition
- ReentrantLock (可重入鎖)
- ReadWriteLock (讀寫鎖)
- 案例
- Lock 與的 Synchronized 區(qū)別
- 九、Callable 接口
- 前言:
- 概述:
- 實(shí)現(xiàn):
- 十、Future 接口
- 概述:
- 實(shí)現(xiàn):
- FutureTask
- FutureTask介紹
- FutureTask應(yīng)用場景及注意事項(xiàng)
- 使用 Callable 和 Future🚩
- 十一、JUC三大常用工具類
- CountDownLatch(減計(jì)數(shù)器)
- 概述:
- 案例:
- 小結(jié):
- CyclicBarrier(加法計(jì)數(shù)器)
- 概述:
- 案例:
- 小結(jié):
- Semaphore( 信號燈)
- 概述:
- 案例:
- 小結(jié):
- 簡單講述 | Phaser & Exchanger
- Phaser
- Exchanger
- 十二、總結(jié)
一、背景
如果你對多線程沒什么了解,那么從入門模塊開始。 如果你已經(jīng)入門了多線程(知道基礎(chǔ)的線程創(chuàng)建、死鎖、synchronized、lock等),那么從juc模塊開始。
新手學(xué)技術(shù)、老手學(xué)體系,高手學(xué)格局。
二、什么是JUC?
JUC實(shí)際上就是我們對于jdk中java.util .concurrent 工具包的簡稱
,其結(jié)構(gòu)如下:
這個(gè)包下都是Java處理線程相關(guān)的類,自jdk1.5后出現(xiàn)。目的就是為了更好的支持高并發(fā)任務(wù)。讓開發(fā)者進(jìn)行多線程編程時(shí)減少競爭條件和死鎖的問題!
JUC主要是指JDK8中java.util.concurrent里提供的一系列線程并發(fā)工具,但是線程并發(fā)的問題遠(yuǎn)不止幾個(gè)工具這么簡單。要學(xué)習(xí)工具使用,更要能深入理解工具的原理以及處理線程并發(fā)問題的思路。
三、JUC框架結(jié)構(gòu)
UC是包的簡稱,JUC可能也是Java核心里最難的一塊兒,JUC指的是Java的并發(fā)工具包,里邊提供了各種各樣的控制同步和線程通信的工具類。學(xué)習(xí)JUC之前,最重要的是了解JUC的結(jié)構(gòu)是什么樣的。就如同Java的集合框架的結(jié)構(gòu)一樣,JUC也有自己框架結(jié)構(gòu),只是往往被大家忽略,筆者就簡單的梳理了下JUC的框架結(jié)構(gòu),JUC的框架結(jié)構(gòu)不同于集合,它并非是實(shí)現(xiàn)繼承框架結(jié)構(gòu)。
四、JUC框架概述
JUC框架的底層在Java代碼里是Unsafe,而Unsafe是底層Jvm的實(shí)現(xiàn)。有了Unsafe的支持實(shí)現(xiàn)了一些了支持原子型操作的Atomic類,然后上層才有了我們熟知的AQS,和LockSupport等類。有了這些之后才是各種讀寫鎖各種線程通信以及同步工具的實(shí)現(xiàn)類。
五、JUC中常用類匯總
-
JUC的atomic包下運(yùn)用了CAS的AtomicBoolean、AtomicInteger、AtomicReference等原子變量類
-
JUC的locks包下的AbstractQueuedSynchronizer(AQS)以及使用AQS的ReentantLock(顯式鎖)、ReentrantReadWriteLock
-
附:運(yùn)用了AQS的類還有:
Semaphore、CountDownLatch、ReentantLock(顯式鎖)、ReentrantReadWriteLock -
JUC下的一些同步工具類:
CountDownLatch(閉鎖)、Semaphore(信號量)、CyclicBarrier(柵欄)、FutureTask -
JUC下的一些并發(fā)容器類:
ConcurrentHashMap、CopyOnWriteArrayList -
讀寫分離:
CopyOnWriteArrayList
寫操作在一個(gè)復(fù)制的數(shù)組上進(jìn)行,讀操作還是在原始數(shù)組中進(jìn)行,讀寫分離,互不影響。
寫操作需要加鎖,防止并發(fā)寫入時(shí)導(dǎo)致寫入數(shù)據(jù)丟失。
寫操作結(jié)束之后需要把原始數(shù)組指向新的復(fù)制數(shù)組。 -
JUC下的一些Executor框架的相關(guān)類:
線程池的工廠類->Executors 線程池的實(shí)現(xiàn)類->ThreadPoolExecutor/ForkJoinPool -
JUC下的一些阻塞隊(duì)列實(shí)現(xiàn)類:
ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue
- tools(工具類):又叫信號量三組工具類,包含有
-
CountDownLatch(閉鎖) 是一個(gè)同步輔助類,在完成一組正在其他線程中執(zhí)行的操作之前,它允許一個(gè)或多個(gè)線程一直等待
-
CyclicBarrier(柵欄) 之所以叫barrier,是因?yàn)槭且粋€(gè)同步輔助類,允許一組線程互相等待,直到到達(dá)某個(gè)公共屏障點(diǎn) ,并且在釋放等待線程后可以重用。
-
Semaphore(信號量) 是一個(gè)計(jì)數(shù)信號量,它的本質(zhì)是一個(gè)“共享鎖“。信號量維護(hù)了一個(gè)信號量許可集。線程可以通過調(diào)用 acquire()來獲取信號量的許可;當(dāng)信號量中有可用的許可時(shí),線程能獲取該許可;否則線程必須等待,直到有可用的許可為止。 線程可以通過release()來釋放它所持有的信號量許可。
- executor(執(zhí)行者):是Java里面線程池的頂級接口,但它只是一個(gè)執(zhí)行線程的工具,真正的線程池接口是ExecutorService,里面包含的類有:
- ScheduledExecutorService 解決那些需要任務(wù)重復(fù)執(zhí)行的問題
- ScheduledThreadPoolExecutor 周期性任務(wù)調(diào)度的類實(shí)現(xiàn)
-
atomic(原子性包):是JDK提供的一組原子操作類,包含有AtomicBoolean、AtomicInteger、AtomicIntegerArray等原子變量類,他們的實(shí)現(xiàn)原理大多是持有它們各自的對應(yīng)的類型變量value,而且被volatile關(guān)鍵字修飾了。這樣來保證每次一個(gè)線程要使用它都會(huì)拿到最新的值。
-
locks(鎖包):是JDK提供的鎖機(jī)制,相比synchronized關(guān)鍵字來進(jìn)行同步鎖,功能更加強(qiáng)大,它為鎖提供了一個(gè)框架,該框架允許更靈活地使用鎖包含的實(shí)現(xiàn)類有:
-
ReentrantLock 它是獨(dú)占鎖,是指只能被獨(dú)自占領(lǐng),即同一個(gè)時(shí)間點(diǎn)只能被一個(gè)線程鎖獲取到的鎖。
-
ReentrantReadWriteLock 它包括子類ReadLock和WriteLock。ReadLock是共享鎖,而WriteLock是獨(dú)占鎖。
-
LockSupport 它具備阻塞線程和解除阻塞線程的功能,并且不會(huì)引發(fā)死鎖。
- collections(集合類):主要是提供線程安全的集合, 比如:
-
ArrayList對應(yīng)的高并發(fā)類是CopyOnWriteArrayList,
-
HashSet對應(yīng)的高并發(fā)類是 CopyOnWriteArraySet,
-
HashMap對應(yīng)的高并發(fā)類是ConcurrentHashMap等等
六、相關(guān)名詞
進(jìn)程和線程
進(jìn)程
概述:
進(jìn)程(Process) 是計(jì)算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)
。 在當(dāng)代面向線程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是線程的容器。程序是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程是程序的實(shí)體
。
定義:
狹義定義:進(jìn)程是正在運(yùn)行的程序的實(shí)例(an instance of a computer program that is being executed)
。
廣義定義:進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng)。它是操作系統(tǒng)動(dòng)態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中,進(jìn)程既是基本的分配單元,也是基本的執(zhí)行單元
。
線程
線程(thread) 是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之 中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流, 一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。
- 線程是獨(dú)立調(diào)度和分派的基本單位。
- 同一進(jìn)程中的多條線程將共享該進(jìn)程中的全部系統(tǒng)資源。
- 一個(gè)進(jìn)程可以有很多線程,每條線程并行執(zhí)行不同的任務(wù)。可并發(fā)執(zhí)行。
當(dāng)然,我們Java都知道的線程的同步是Java多線程編程的難點(diǎn),因?yàn)橐o哪些地方是共享資源(競爭資源),什么時(shí)候要考慮同步等,都是編程中的難點(diǎn)。
創(chuàng)建線程的幾種常見的方式
- 通過實(shí)現(xiàn)Runnable接口來創(chuàng)建Thread線程
- 通過繼承Thread類來創(chuàng)建一個(gè)線程
- 通過實(shí)現(xiàn)Callable接口來創(chuàng)建Thread線程
備注:詳細(xì)看我寫的上一篇文章:Java實(shí)現(xiàn)多線程的4種方式
并發(fā)和并行
在了解并發(fā)和并行之前,讓我們先來看一看串行是什么樣的吧。
- 串行模式:
- 串行模式:即表示所有任務(wù)都是按先后順序進(jìn)行。串行是一次只能取的一個(gè)任務(wù),并執(zhí)行這個(gè)任務(wù)。
- 舉個(gè)生活中的小例子:就是在火車站買票,今天只開放這一個(gè)窗口賣票,那么我們只有等到前面的人都買了,才能輪到我們?nèi)ベI。即按先后順序買到票。
- 并行模式:
- 概述:一組程序按獨(dú)立異步的速度執(zhí)行,無論從微觀還是宏觀,程序都是一起執(zhí)行的。對比地,并發(fā)是指:在同一個(gè)時(shí)間段內(nèi),兩個(gè)或多個(gè)程序執(zhí)行,有時(shí)間上的重疊(宏觀上是同時(shí),微觀上仍是順序執(zhí)行)。
- 并行模式:并行意味著可以同時(shí)取得多個(gè)任務(wù),并同時(shí)去執(zhí)行所取得的這些任務(wù)。
我們還是用上面那個(gè)例子:還是在買票,以前是只有一個(gè)窗口賣票,但是近幾年發(fā)展起來了,現(xiàn)在有五個(gè)窗口賣票啦,大大縮短了人們買票的時(shí)間。 - 并行模式相當(dāng)于將長長的一條隊(duì)列,劃分成了多條短隊(duì)列,所以并行縮短了任務(wù)隊(duì)列的長度。不過并行的效率,一方面受多進(jìn)程/線程編碼的好壞的影響,另一方面也受硬件角度上的CPU的影響。
- 并發(fā):
- 并發(fā):并發(fā)指的是多個(gè)程序可以同時(shí)運(yùn)行的一種現(xiàn)象,并發(fā)的重點(diǎn)在于它是一種現(xiàn)象,并發(fā)描述的是多進(jìn)程同時(shí)運(yùn)行的現(xiàn)象。但真正意義上,一個(gè)單核心CPU任一時(shí)刻都只能運(yùn)行一個(gè)線程。所以此處的"同時(shí)運(yùn)行"表示的不是真的同一時(shí)刻有多個(gè)線程運(yùn)行的現(xiàn)象(這是并行的概念),而是提供了一種功能讓用戶看來多個(gè)程序同時(shí)運(yùn)行起來了,但實(shí)際上這些程序中的進(jìn)程不是一直霸占 CPU 的,而是根據(jù)CPU的調(diào)度,執(zhí)行一會(huì)兒停一會(huì)兒。
- 小小的總結(jié)一下:
-
并發(fā):即同一時(shí)刻多個(gè)線程在訪問同一個(gè)資源,多個(gè)線程對一個(gè)點(diǎn)
例子:秒殺活動(dòng)、12306搶回家的票啦、搶演唱會(huì)的票… -
并行:多個(gè)任務(wù)一起執(zhí)行,之后再匯總
例子:電飯煲煮飯、用鍋炒菜,兩個(gè)事情一起進(jìn)行,(最后我們一起干飯啦干飯啦😁)
用戶線程和守護(hù)線程
-
用戶線程:指不需要內(nèi)核支持而在用戶程序中實(shí)現(xiàn)的線程,其不依賴于操作系統(tǒng)核心,應(yīng)用進(jìn)程利用線程庫提供創(chuàng)建、同步、調(diào)度和管理線程的函數(shù)來控制用戶線程。
-
守護(hù)線程:是指在程序運(yùn)行的時(shí)候在后臺提供一種通用服務(wù)的線程,用來服務(wù)于用戶線程;不需要上層邏輯介入,當(dāng)然我們也可以手動(dòng)創(chuàng)建一個(gè)守護(hù)線程。(用白話來說:就是守護(hù)著用戶線程,當(dāng)用戶線程死亡,守護(hù)線程也會(huì)隨之死亡)
比如垃圾回收線程就是一個(gè)很稱職的守護(hù)者,并且這種線程并不屬于程序中不可或缺的部分。因此,當(dāng)所有的非守護(hù)線程結(jié)束時(shí),程序也就終止了,同時(shí)會(huì)殺死進(jìn)程中的所有守護(hù)線程。反過來說,只要任何非守護(hù)線程還在運(yùn)行,程序就不會(huì)終止。
用一個(gè)簡單代碼來模擬一下:
未設(shè)置為守護(hù)線程時(shí):主線程執(zhí)行完成了,但是我們自己創(chuàng)建的線程仍然未結(jié)束。
設(shè)置為守護(hù)線程后:明顯可以看到,當(dāng)主線程執(zhí)行完成后,我們設(shè)置為守護(hù)線程的那個(gè)線程也被強(qiáng)制結(jié)束了。
setDaemon就是設(shè)置為是否為守護(hù)線程。
七、synchronized 作用范圍:
synchronized 是 Java 中的關(guān)鍵字,是一種同步鎖。它修飾的對象有以下幾種:
- 修飾某一處代碼塊,被修飾的代碼塊稱為同步語句塊。作用范圍就是{}之間。作用的對象是調(diào)用這個(gè)代碼塊的對象。
synchronized (this){System.out.println("同步代碼塊 ");
}
- 修飾在方法上,被修飾的方法就稱為同步方法。作用范圍則是整個(gè)方法。作用的對象則是調(diào)用這個(gè)方法的對象。
public synchronized void sale() {}
注:synchronized 關(guān)鍵字不能被繼承,如果父類中某方法使用了synchronized 關(guān)鍵字,字類又正巧覆蓋了,此時(shí),字類默認(rèn)情況下是不同步的,必須顯示的在子類的方法上加上才可。當(dāng)然,如果在字類中調(diào)用父類中的同步方法,這樣雖然字類并沒有同步方法,但子類調(diào)用父類的同步方法,子類方法也相當(dāng)同步了。
- 修飾某個(gè)靜態(tài)的方法,其作用的范圍是整個(gè)靜態(tài)方法,作用的對象是這個(gè)類的所有對象。
public static synchronized void test(){}
- 修飾某個(gè)類,其作用的范圍是 synchronized 后面括號括起來的部分,作用的對象是這個(gè)類的所有對象。
class Ticket {public void sale() {synchronized (Ticket.class) {}}
}
八、Lock鎖(重點(diǎn))
什么是 Lock
Lock 鎖實(shí)現(xiàn)提供了比使用同步方法和語句可以獲得的更廣泛的鎖操作。
鎖類型
可重入鎖:在執(zhí)行對象中所有同步方法不用再次獲得鎖
可中斷鎖:在等待獲取鎖過程中可中斷
公平鎖: 按等待獲取鎖的線程的等待時(shí)間進(jìn)行獲取,等待時(shí)間長的具有優(yōu)先獲取鎖權(quán)利
讀寫鎖:對資源讀取和寫入的時(shí)候拆分為2部分處理,讀的時(shí)候可以多線程一起讀,寫的時(shí)候必須同步地寫
Lock接口
public interface Lock {void lock(); //獲得鎖。/**除非當(dāng)前線程被中斷,否則獲取鎖。如果可用,則獲取鎖并立即返回。如果鎖不可用,則當(dāng)前線程將出于線程調(diào)度目的而被禁用并處于休眠狀態(tài),直到發(fā)生以下兩種情況之一:鎖被當(dāng)前線程獲取; 要么其他一些線程中斷當(dāng)前線程,支持中斷獲取鎖。如果當(dāng)前線程:在進(jìn)入此方法時(shí)設(shè)置其中斷狀態(tài); 要么獲取鎖時(shí)中斷,支持中斷獲取鎖,*/void lockInterruptibly() throws InterruptedException; /**僅在調(diào)用時(shí)空閑時(shí)才獲取鎖。如果可用,則獲取鎖并立即返回值為true 。 如果鎖不可用,則此方法將立即返回false值。*/boolean tryLock();//比上面多一個(gè)等待時(shí)間 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 解鎖void unlock(); //返回綁定到此Lock實(shí)例的新Condition實(shí)例。Condition newCondition(); 。
}
下面講幾個(gè)常用方法的使用。
lock()、unlock()
lock()是最常用的方法之一,作用就是獲取鎖,如果鎖已經(jīng)被其他線程獲得,則當(dāng)前線程將被禁用以進(jìn)行線程調(diào)度,并處于休眠狀態(tài),等待,直到獲取鎖。
如果使用到了lock的話,那么必須去主動(dòng)釋放鎖,就算發(fā)生了異常,也需要我們主動(dòng)釋放鎖,因?yàn)閘ock并不會(huì)像synchronized一樣被自動(dòng)釋放。所以使用lock的話,必須是在try{}catch(){}中進(jìn)行,并將釋放鎖的代碼放在finally{}中,以確保鎖一定會(huì)被釋放,以防止死鎖現(xiàn)象的發(fā)生。
unlock()的作用就是主動(dòng)釋放鎖。
lock接口的類型有好幾個(gè)實(shí)現(xiàn)類,這里是隨便找了個(gè)。
Lock lock = new ReentrantLock();
try {lock.lock();System.out.println("上鎖了");
}catch (Exception e){e.printStackTrace();
}finally {lock.unlock();System.out.println("解鎖了");
}
newCondition
關(guān)鍵字 synchronized 與 wait()/notify()這兩個(gè)方法一起使用可以實(shí)現(xiàn)等待/通知模式, Lock 鎖的 newContition()方法返回 Condition 對象,Condition 類 也可以實(shí)現(xiàn)等待/通知模式。 用 notify()通知時(shí),JVM 會(huì)隨機(jī)喚醒某個(gè)等待的線程, 使用 Condition 類可以 進(jìn)行選擇性通知, Condition 比較常用的兩個(gè)方法:
- await():會(huì)使當(dāng)前線程等待,同時(shí)會(huì)釋放鎖,當(dāng)?shù)鹊狡渌€程調(diào)用signal()方法時(shí),此時(shí)這個(gè)沉睡線程會(huì)重新獲得鎖并繼續(xù)執(zhí)行代碼(在哪里沉睡就在哪里喚醒)。
- signal():用于喚醒一個(gè)等待的線程。
注意:在調(diào)用 Condition 的 await()/signal()方法前,也需要線程持有相關(guān) 的 Lock 鎖,調(diào)用 await()后線程會(huì)釋放這個(gè)鎖,在調(diào)用singal()方法后會(huì)從當(dāng)前 Condition對象的等待隊(duì)列中,喚醒一個(gè)線程,后被喚醒的線程開始嘗試去獲得鎖, 一旦成功獲得鎖就繼續(xù)往下執(zhí)行。
在這個(gè)地方我們舉個(gè)例子來用代碼寫一下:
這里就不舉例synchronized 實(shí)現(xiàn)了,道理都差不多。
例子:我們有兩個(gè)線程,實(shí)現(xiàn)對一個(gè)初始值是0的number變量,一個(gè)線程當(dāng)number = =0時(shí) 對number值+1,另外一個(gè)線程當(dāng)number = = 1時(shí)對number-1。
class Share {private Integer number = 0;private ReentrantLock lock = new ReentrantLock();private Condition newCondition = lock.newCondition();// +1 的方法public void incr() {try {lock.lock(); // 加鎖while (number != 0) {newCondition.await();//沉睡}number++;System.out.println(Thread.currentThread().getName() + "::" + number);newCondition.signal(); //喚醒另一個(gè)沉睡的線程 } catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}// -1 的方法public void decr() {try {lock.lock();while (number != 1) {newCondition.await();}number--;System.out.println(Thread.currentThread().getName() + "::" + number);newCondition.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}public class LockDemo2 {public static void main(String[] args) {Share share = new Share();new Thread(()->{for (int i=0;i<=10;i++){share.incr();}},"AA").start();new Thread(()->{for (int i=0;i<=10;i++){share.decr();}},"BB").start();/*** AA::1* BB::0* AA::1* BB::0* .....*/ }
}
ReentrantLock (可重入鎖)
ReentrantLock,意思是“可重入鎖”。ReentrantLock 是唯一實(shí)現(xiàn)了 Lock 接口的類,并且 ReentrantLock 提供了更 多的方法。
可重入鎖:什么是 “可重入”,可重入就是說某個(gè)線程已經(jīng)獲得某個(gè)鎖,可以再次獲取鎖而不會(huì)出現(xiàn)死鎖
package com.crush.juc02;import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockDemo {public static void main(String[] args) {ReentrantLock lock = new ReentrantLock();new Thread(new Runnable() {@Overridepublic void run() {try {lock.lock();System.out.println("第1次獲取鎖,這個(gè)鎖是:" + lock);for (int i = 2;i<=11;i++){try {lock.lock();System.out.println("第" + i + "次獲取鎖,這個(gè)鎖是:" + lock);try {Thread.sleep(new Random().nextInt(200));} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();// 如果把這里注釋掉的話,那么程序就會(huì)陷入死鎖當(dāng)中。}}} finally {lock.unlock();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {try {lock.lock();System.out.println("這里是為了測試死鎖而多寫一個(gè)的線程");} finally {lock.unlock();}}}).start();}
}
/*** 第1次獲取鎖,這個(gè)鎖是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0]* 第2次獲取鎖,這個(gè)鎖是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0]* 第3次獲取鎖,這個(gè)鎖是:java.util.concurrent.locks.ReentrantLock@6b5fde1f[Locked by thread Thread-0]* ...*/
死鎖的話,程序就無法停止,直到資源耗盡或主動(dòng)終止。
代碼中也稍微提了一下死鎖的概念,在使用Lock中必須手動(dòng)解鎖,不然就會(huì)可能造成死鎖的現(xiàn)象。
ReadWriteLock (讀寫鎖)
ReadWriteLock 也是一個(gè)接口,在它里面只定義了兩個(gè)方法:
public interface ReadWriteLock {// 獲取讀鎖Lock readLock();// 獲取寫鎖Lock writeLock();
}
分為一個(gè)讀鎖一個(gè)寫鎖,將讀寫進(jìn)行了分離,使可以多個(gè)線程進(jìn)行讀操作,從而提高了效率。
ReentrantReadWriteLock 實(shí)現(xiàn)了 ReadWriteLock 接口。里面提供了更豐富的方法,當(dāng)然最主要的還是獲取寫鎖(writeLock)和讀鎖(readLock)。
案例
假如多個(gè)線程要進(jìn)行讀的操作,我們用Synchronized 來實(shí)現(xiàn)的話。
public class SynchronizedDemo2 {public static void main(String[] args) {final SynchronizedDemo2 test = new SynchronizedDemo2();new Thread(()->{test.get(Thread.currentThread());}).start();new Thread(()->{test.get(Thread.currentThread());}).start();}public synchronized void get(Thread thread) {long start = System.currentTimeMillis();while(System.currentTimeMillis() - start <= 1) {System.out.println(thread.getName()+"正在進(jìn)行讀操作");}System.out.println(thread.getName()+"讀操作完畢");}
}
/*** 輸出* Thread-0正在進(jìn)行讀操作* Thread-0讀操作完畢* Thread-1正在進(jìn)行讀操作* Thread-1正在進(jìn)行讀操作* Thread-1正在進(jìn)行讀操作* ....* Thread-1讀操作完畢*/
改成讀寫鎖之后
public class SynchronizedDemo2 {private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();public static void main(String[] args) {final SynchronizedDemo2 test = new SynchronizedDemo2();new Thread(()->{test.get2(Thread.currentThread());}).start();new Thread(()->{test.get2(Thread.currentThread());}).start();}public void get2(Thread thread) {rwl.readLock().lock();try {long start = System.currentTimeMillis();while(System.currentTimeMillis() - start <= 1) {System.out.println(thread.getName()+"正在進(jìn)行讀操作");}System.out.println(thread.getName()+"讀操作完畢");} finally {rwl.readLock().unlock();}}
}
/*** 輸出* Thread-0正在進(jìn)行讀操作* Thread-0讀操作完畢* Thread-1正在進(jìn)行讀操作* Thread-1讀操作完畢*/
結(jié)論:改用讀寫鎖后 線程1和線程2 同時(shí)在讀,可以感受到效率的明顯提升。
注意:
- 若此時(shí)已經(jīng)有一個(gè)線程占用了讀鎖,此時(shí)其他線程申請讀鎖是可以的,但是若此時(shí)其他線程申請寫鎖,則只有等待讀鎖釋放,才能成功獲得。
- 若此時(shí)已經(jīng)有一個(gè)線程占用了寫鎖,那么此時(shí)其他線程申請寫鎖或讀鎖,都只有持有寫鎖的線程釋放寫鎖,才能成功獲得。
Lock 與的 Synchronized 區(qū)別
類別 | synchronized | Lock |
---|---|---|
存在層次 | Java的關(guān)鍵字,在jvm層面上 | 是一個(gè)接口 |
鎖的獲取 | 假設(shè)A線程獲得鎖,B線程等待。如果A線程阻塞,B線程會(huì)一直等待 | 分情況而定,Lock有多個(gè)鎖獲取的方式,具體下面會(huì)說道,大致就是可以嘗試獲得鎖,線程可以不用一直等待 |
鎖的釋放 | 1、當(dāng) synchronized 方法或者 synchronized 代碼塊執(zhí)行完之后, 系統(tǒng)會(huì)自動(dòng)讓線程釋放對鎖的占用 (不需要手動(dòng)釋放鎖)2、若線程執(zhí)行發(fā)生異常,jvm會(huì)讓線程釋放鎖 | 在finally中必須釋放鎖,不然容易造成線程死鎖現(xiàn)象 (需要手動(dòng)釋放鎖) |
鎖狀態(tài) | 無法判斷 | 可以判斷 |
鎖類型 | 鎖類型 | 可重入 可判斷 可公平(兩者皆可) |
性能 | 前提:大量線程情況下 同步效率較低 | 前提:大量線程情況下 同步效率比synchronized高的多 |
Lock可以提高多個(gè)線程進(jìn)行讀操作的效率。
九、Callable 接口
前言:
在上上篇文章中,創(chuàng)建線程那個(gè)小角落,提到了這個(gè),但是當(dāng)時(shí)只是匆匆忙忙講了一下。到這里再全面性的講解一下。
我們以前使用實(shí)現(xiàn)Runnable接口的方式來創(chuàng)建線程,但是Runnable的run() 存在一個(gè)缺陷問題,就是不能將執(zhí)行完的結(jié)果返回。
Java就是為了能夠?qū)崿F(xiàn)這個(gè)功能,在jdk1.5中提出了Callable接口。
概述:
Callable 接口位于java.util.concurrent包下。
@FunctionalInterface
public interface Callable<V> {V call() throws Exception; //計(jì)算結(jié)果,如果無法計(jì)算則拋出異常。
}
Callable 類似于Runnable 接口,但Runnable 接口中的run()方法不會(huì)返回結(jié)果,并且也無法拋出經(jīng)過檢查的異常,但是Callable中call()方法能夠返回計(jì)算結(jié)果,并且也能夠拋出經(jīng)過檢查的異常。
實(shí)現(xiàn):
通過實(shí)現(xiàn)Callable接口創(chuàng)建線程詳細(xì)步驟:Runnable直接看代碼就知道了哈。
-
創(chuàng)建實(shí)現(xiàn)Callable接口的類SomeCallable
-
創(chuàng)建一個(gè)類對象:Callable oneCallable = new SomeCallable();
-
由Callable創(chuàng)建一個(gè)FutureTask對象:FutureTask futureTask= new FutureTask(oneCallable);
注釋:FutureTask是一個(gè)包裝器,它通過接受Callable來創(chuàng)建,它同時(shí)實(shí)現(xiàn)了Future和Runnable接口。 -
由FutureTask創(chuàng)建一個(gè)Thread對象:Thread oneThread = new Thread(futureTask);
-
啟動(dòng)線程:oneThread.start();
public class Demo1 {public static void main(String[] args) {new Thread(new RunnableDemo1(),"AA").start();FutureTask<Integer> futureTask = new FutureTask<>(new CallableDemo<Integer>());new Thread(futureTask,"BB").start();// 在線程執(zhí)行完后,我們可以通過futureTask的get方法來獲取到返回的值。System.out.println(futureTask.get());}
}
class RunnableDemo1 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"::通過實(shí)現(xiàn)Runnable來執(zhí)行任務(wù)");}
}
class CallableDemo<Integer> implements Callable<java.lang.Integer> {@Overridepublic java.lang.Integer call() throws Exception {System.out.println(Thread.currentThread().getName()+"::通過實(shí)現(xiàn)Callable接口來執(zhí)行任務(wù),并返回結(jié)果!");return 1024;}
}
/*** AA::通過實(shí)現(xiàn)Runnable來執(zhí)行任務(wù)* BB::通過實(shí)現(xiàn)Callable接口來執(zhí)行任務(wù),并返回結(jié)果!* 1024*/
這里之所以要轉(zhuǎn)成 FutureTask 放進(jìn) Thread中去,是因?yàn)镃allable 本身與Thread沒有關(guān)系,通過FutureTask 才能和Thread產(chǎn)生聯(lián)系。
十、Future 接口
概述:
Future 接口同樣位于java.util.concurrent包下。
Future接口提供方法來檢測任務(wù)是否被執(zhí)行完,等待任務(wù)執(zhí)行完獲得結(jié)果,也可以設(shè)置任務(wù)執(zhí)行的超時(shí)時(shí)間。這個(gè)設(shè)置超時(shí)的方法就是實(shí)現(xiàn)Java程序執(zhí)行超時(shí)的關(guān)鍵。
public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning); //嘗試取消此任務(wù)的執(zhí)行。boolean isCancelled();//如果此任務(wù)在正常完成之前被取消,則返回true boolean isDone(); //如果此任務(wù)完成,則返回true 。 完成可能是由于正常終止、異常或取消——在所有這些情況下,此方法將返回true V get() throws InterruptedException, ExecutionException; //獲得任務(wù)計(jì)算結(jié)果V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;//可等待多少時(shí)間去獲得任務(wù)計(jì)算結(jié)果
}
實(shí)現(xiàn):
Future模式通俗點(diǎn)來描述就是:我有一個(gè)任務(wù),提交給了Future,Future替我完成這個(gè)任務(wù)。期間我自己可以去做任何想做的事情。一段時(shí)間之后,我就便可以從Future那兒取出結(jié)果。
public class Demo1 {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<Integer> futureTask = new FutureTask<>(new CallableDemo<Integer>());new Thread(futureTask,"BB").start();System.out.println(futureTask.get());// 我們來測試一下任務(wù)是否已經(jīng)完成System.out.println(futureTask.isDone());}
}
class CallableDemo<Integer> implements Callable<java.lang.Integer> {@Overridepublic java.lang.Integer call() throws Exception {System.out.println(Thread.currentThread().getName()+"::通過實(shí)現(xiàn)Callable接口來執(zhí)行任務(wù),并返回結(jié)果!");return 1024;}
}
Future 用于存儲從另一個(gè)線程獲得的結(jié)果。如果只是簡單創(chuàng)建線程,直接使用Runnable就可以,想要獲得任務(wù)返回值,就用Future。
FutureTask
FutureTask介紹
位于 java.util.concurrent包下。
可取消的異步計(jì)算。 此類提供Future的基本實(shí)現(xiàn),具有啟動(dòng)和取消計(jì)算、查詢以查看計(jì)算是否完成以及檢索計(jì)算結(jié)果的方法。 計(jì)算完成后才能檢索結(jié)果; 如果計(jì)算尚未完成, get方法將阻塞。 一旦計(jì)算完成,就不能重新開始或取消計(jì)算(除非使用runAndReset調(diào)用計(jì)算)。結(jié)構(gòu)圖:
FutureTask實(shí)現(xiàn)了 Runnable 和 Future接口,并方便地將兩種功能組合在一起。并且通過構(gòu)造函數(shù)提供Callable來創(chuàng)建FutureTask,就可以提供給Thread來創(chuàng)建線程啦。
FutureTask有以下三種狀態(tài):
- 未啟動(dòng)狀態(tài):還未執(zhí)行run()方法。
- 已啟動(dòng)狀態(tài):已經(jīng)在執(zhí)行run()方法。
- 完成狀態(tài):已經(jīng)執(zhí)行完run()方法,或者被取消了,亦或者方法中發(fā)生異常而導(dǎo)致中斷結(jié)束。
FutureTask應(yīng)用場景及注意事項(xiàng)
應(yīng)用場景:
- 在主線程執(zhí)行那種比較耗時(shí)的操作時(shí),但同時(shí)又不能去阻塞主線程時(shí),就可以將這樣的任務(wù)交給FutureTask對象在后臺完成,然后等之后主線程需要的時(shí)候,就可以直接get()來獲得返回?cái)?shù)據(jù)或者通過isDone()來獲得任務(wù)的狀態(tài)。
- 一般FutureTask多應(yīng)用于耗時(shí)的計(jì)算,這樣主線程就可以把一個(gè)耗時(shí)的任務(wù)交給FutureTask,然后等到完成自己的任務(wù)后,再去獲取計(jì)算結(jié)果
注意:
- 僅在計(jì)算完成時(shí)才能檢索結(jié)果;如果計(jì)算尚未完成,則阻塞 get 方法。
- 一旦計(jì) 算完成,就不能再重新開始或取消計(jì)算。
- get 方法而獲取結(jié)果只有在計(jì)算完成時(shí)獲取,否則會(huì)一直阻塞直到任務(wù)轉(zhuǎn)入完成狀態(tài),然后會(huì)返回結(jié)果或者拋出異常。
- 因?yàn)橹粫?huì)計(jì)算一次,因此通常get方法放到最后。
使用放在下一小節(jié)啦👇
使用 Callable 和 Future🚩
這里的使用其實(shí)在上文已經(jīng)提到過了,這里就將其更完善一些吧。
public class CallableDemo2 {public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {CallableAndFutureTest callableAndFutureTest = new CallableAndFutureTest();FutureTask<String> task = new FutureTask<>(callableAndFutureTest);new Thread(task).start();// System.out.println("嘗試取消任務(wù),傳true表示取消任務(wù),false則不取消任務(wù)::"+task.cancel(true));System.out.println("判斷任務(wù)是否已經(jīng)完成::"+task.isDone());//結(jié)果已經(jīng)計(jì)算出來,則立馬取出來,如若摸沒有計(jì)算出來,則一直等待,直到結(jié)果出來,或任務(wù)取消或發(fā)生異常。System.out.println("阻塞式獲取結(jié)果::"+task.get());System.out.println("在獲取結(jié)果時(shí),給定一個(gè)等待時(shí)間,如果超過等待時(shí)間還未獲取到結(jié)果,則會(huì)主動(dòng)拋出超時(shí)異常::"+task.get(2, TimeUnit.SECONDS));}
}class CallableAndFutureTest implements Callable<String> {@Overridepublic String call() throws Exception {String str="";for (int i=0;i<10;i++){str+=String.valueOf(i);Thread.sleep(100);}return str;}
}
十一、JUC三大常用工具類
CountDownLatch(減計(jì)數(shù)器)
概述:
CountDownLatch位于 java.util.concurrent包下。
CountDownLatch是一個(gè)同步輔助類,允許一個(gè)或多個(gè)線程等待,一直到其他線程執(zhí)行的操作完成后再執(zhí)行。
CountDownLatch是通過一個(gè)計(jì)數(shù)器來實(shí)現(xiàn)的,計(jì)數(shù)器的初始值是線程的數(shù)量。每當(dāng)有一個(gè)線程執(zhí)行完畢后,然后通過 countDown 方法來讓計(jì)數(shù)器的值-1,當(dāng)計(jì)數(shù)器的值為0時(shí),表示所有線程都執(zhí)行完畢,然后繼續(xù)執(zhí)行 await 方法 之后的語句,即在鎖上等待的線程就可以恢復(fù)工作了。
CountDownLatch中主要有兩個(gè)方法:
- countDown:
- 遞減鎖存器的計(jì)數(shù),如果計(jì)數(shù)達(dá)到零,則釋放所有等待的線程。
- 如果當(dāng)前計(jì)數(shù)大于零,則遞減。 如果新計(jì)數(shù)為零,則為線程調(diào)度目的重新啟用所有等待線程。
- 如果當(dāng)前計(jì)數(shù)為零,則什么也不會(huì)發(fā)生。
public void countDown() {sync.releaseShared(1);
}
- await:
- 使當(dāng)前線程等待直到閂鎖倒計(jì)時(shí)為零,除非線程被中斷。
- 如果當(dāng)前計(jì)數(shù)為零,則此方法立即返回。即await 方法阻塞的線程會(huì)被喚醒,繼續(xù)執(zhí)行
- 如果當(dāng)前計(jì)數(shù)大于零,則當(dāng)前線程出于線程調(diào)度目的而被禁用并處于休眠狀態(tài)
public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
案例:
舉個(gè)生活中的小例子:
我們一寢室人去上課,得等到1、2、3、4、5、6、7、8個(gè)人都出來,才可以鎖上寢室門吧。即當(dāng)計(jì)數(shù)器值為0時(shí),就可以執(zhí)行await的方法啦。
編碼步驟:
- CountDownLatch countDownLatch = new CountDownLatch(8);
- countDownLatch.countDown(); 一個(gè)線程出來一個(gè)人,計(jì)數(shù)器就 -1
- countDownLatch.await(); 阻塞的等待計(jì)數(shù)器歸零
- 執(zhí)行后續(xù)步驟
我們用代碼來模擬一下這個(gè)例子哈:
public class CountDownLatchDemo1 {public static void main(String[] args) {// 初始值8 有八個(gè)人需要出寢室門CountDownLatch countDownLatch = new CountDownLatch(8);for (int i = 1; i <= 8; i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + "出去啦");// 出去一個(gè)人計(jì)數(shù)器就減1countDownLatch.countDown();}, String.valueOf(i)).start();}try {countDownLatch.await(); // 阻塞等待計(jì)數(shù)器歸零} catch (InterruptedException e) {e.printStackTrace();}// 阻塞的操作 : 計(jì)數(shù)器 num++System.out.println(Thread.currentThread().getName() + "====寢室人都已經(jīng)出來了,關(guān)門向教室沖!!!====");}
}
小結(jié):
CountDownLatch使用給定的計(jì)數(shù)進(jìn)行初始化。 由于調(diào)用了countDown方法,每次-1, await方法會(huì)一直阻塞到當(dāng)前計(jì)數(shù)達(dá)到零,然后釋放所有等待線程,并且任何后續(xù)的await調(diào)用都會(huì)立即返回。 這是一種一次性現(xiàn)象——計(jì)數(shù)無法重置。 如果您需要重置計(jì)數(shù)的版本,請考慮使用CyclicBarrier 。
CountDownLatch一個(gè)有用屬性是它不需要調(diào)用countDown線程在繼續(xù)之前等待計(jì)數(shù)達(dá)到零,它只是阻止任何線程通過await,直到所有線程都可以通過。
CyclicBarrier(加法計(jì)數(shù)器)
概述:
CyclicBarrier 看英文單詞就可以看出大概就是循環(huán)阻塞的意思。所以還常稱為循環(huán)柵欄。
CyclicBarrier 主要方法有:
public class CyclicBarrier {private int dowait(boolean timed, long nanos); // 供await方法調(diào)用 判斷是否達(dá)到條件 可以往下執(zhí)行嗎//創(chuàng)建一個(gè)新的CyclicBarrier,它將在給定數(shù)量的參與方(線程)等待時(shí)觸發(fā),每執(zhí)行一次CyclicBarrier就累加1,達(dá)到了parties,就會(huì)觸發(fā)barrierAction的執(zhí)行public CyclicBarrier(int parties, Runnable barrierAction) ;//創(chuàng)建一個(gè)新的CyclicBarrier ,參數(shù)就是目標(biāo)障礙數(shù),它將在給定數(shù)量的參與方(線程)等待時(shí)觸發(fā),每次執(zhí)行 CyclicBarrier 一次障礙數(shù)會(huì)加一,如果達(dá)到了目標(biāo)障礙數(shù),才會(huì)執(zhí)行 cyclicBarrier.await()之后的語句public CyclicBarrier(int parties) //返回觸發(fā)此障礙所需的參與方數(shù)量。public int getParties()//等待,直到所有各方都在此屏障上調(diào)用了await 。// 如果當(dāng)前線程不是最后一個(gè)到達(dá)的線程,那么它會(huì)出于線程調(diào)度目的而被禁用并處于休眠狀態(tài).直到所有線程都調(diào)用了或者被中斷亦或者發(fā)生異常中斷退出public int await()// 基本同上 多了個(gè)等待時(shí)間 等待時(shí)間內(nèi)所有線程沒有完成,將會(huì)拋出一個(gè)超時(shí)異常public int await(long timeout, TimeUnit unit)//將障礙重置為其初始狀態(tài)。 public void reset()}
public CyclicBarrier(int parties):的構(gòu)造方法第一個(gè)參數(shù)是目標(biāo)障礙數(shù),每次執(zhí)行 CyclicBarrier 一次障礙數(shù)會(huì)加一,如果達(dá)到了目標(biāo)障礙數(shù),才會(huì)執(zhí)行 cyclicBarrier.await()之后 的語句??梢詫?CyclicBarrier 理解為加 1 操作。
public CyclicBarrier(int parties, Runnable barrierAction) :的構(gòu)造方法第一個(gè)參數(shù)是目標(biāo)障礙數(shù),每次執(zhí)行 CyclicBarrier 一次障礙數(shù)會(huì)加一,如果達(dá)到了目標(biāo)障礙數(shù),就會(huì)執(zhí)行我們傳入的Runnable;
案例:
我想大家多少玩過王者榮耀吧,里面不是有個(gè)鉆石奪寶嗎,抽201次必得榮耀水晶,這次讓我們用代碼來模擬一下吧。
編程步驟:
-
創(chuàng)建CyclicBarrier對象
CyclicBarrier cyclicBarrier = new CyclicBarrier(count, new MyRunnable()); -
編寫業(yè)務(wù)代碼
-
cyclicBarrier.await(); //在線程里面等待阻塞,累加1,達(dá)到最大值count時(shí),觸發(fā)我們傳入進(jìn)去MyRunnable執(zhí)行。
public class CyclicBarrierDemo1 {public static void main(String[] args) {// 第一個(gè)參數(shù):目標(biāo)障礙數(shù) 第二個(gè)參數(shù):一個(gè)Runnable任務(wù),當(dāng)達(dá)到目標(biāo)障礙數(shù)時(shí),就會(huì)執(zhí)行我們傳入的Runnable// 當(dāng)我們抽了201次的時(shí)候,就會(huì)執(zhí)行這個(gè)任務(wù)。CyclicBarrier cyclicBarrier = new CyclicBarrier(201,()->{System.out.println("恭喜你,已經(jīng)抽獎(jiǎng)201次,幸運(yùn)值已滿,下次抽獎(jiǎng)必中榮耀水晶!!!");});for (int i=1;i<=201;i++){final int count=i;new Thread(()->{System.out.println(Thread.currentThread().getName()+"抽獎(jiǎng)一次");try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}},String.valueOf(i)).start();}}// 這行代碼是重置計(jì)數(shù)cyclicBarrier.reset();// 這里是我又加了 一次循環(huán), 可以看到最后結(jié)果中輸出了兩次 "恭喜你"for (int i=1;i<=201;i++){final int count=i;new Thread(()->{System.out.println(Thread.currentThread().getName()+"抽獎(jiǎng)一次");try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}},String.valueOf(i)).start();}
}
小結(jié):
CyclicBarrier和CountDownLatch其實(shí)非常相似,CyclicBarrier表示加法,CountDownLatch表示減法。
區(qū)別還是有的:
- CyclicBarrier只能夠喚醒一個(gè)任務(wù),CountDownLatch可以喚起多個(gè)任務(wù)。
- CyclicBarrier可以重置,重新使用,但是CountDownLatch的值等于0時(shí),就不可重復(fù)用了。
Semaphore( 信號燈)
概述:
Semaphore:信號量通常用于限制可以訪問某些(物理或邏輯)資源的線程數(shù)。
使用場景:
限制資源,如搶位置、限流等。
案例:
【例子】:
不知道大家有沒有過在網(wǎng)吧搶電腦打游戲的那種經(jīng)歷,小時(shí)候,平常便宜點(diǎn)的網(wǎng)吧都比較小,而且也比較少,特別多的人去,去晚了的人就只有站在那里看,等別人下機(jī)才能上網(wǎng)。
這次的例子就是:網(wǎng)吧有十臺高配置打游戲的電腦,有20個(gè)小伙伴想要上網(wǎng)。
我們用代碼來模擬一下:
編程步驟:
-
創(chuàng)建信號燈
Semaphore semaphore = new Semaphore(10); // 5個(gè)位置 -
等待獲取信號燈
semaphore.acquire();//等待獲取許可證 -
業(yè)務(wù)代碼
-
釋放信號
semaphore.release();//釋放資源,女朋友來找了,下機(jī)下機(jī),陪女朋友去了,那么就要釋放這臺電腦啦
public class SemaphoreDemo1 {public static void main(String[] args) {// 10臺電腦Semaphore semaphore = new Semaphore(10);// 20 個(gè)小伙伴想要上網(wǎng)for (int i = 1; i <= 20; i++) {new Thread(() -> {try {//等待獲取許可證semaphore.acquire();System.out.println(Thread.currentThread().getName() + "搶到了電腦");//搶到的小伙伴,迅速就開打啦 這里就模擬個(gè)時(shí)間哈,TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();} finally {//打完幾把游戲的小伙伴 女朋友來找了 溜啦溜啦 希望大家都有人陪伴System.out.println("女朋友來找,"+Thread.currentThread().getName() + "離開了");semaphore.release();//釋放資源,離開了就要把電腦讓給別人啦。}}, String.valueOf(i)).start();}}
}
小結(jié):
- 在獲得一個(gè)項(xiàng)目之前,每個(gè)線程必須從信號量中獲得一個(gè)許可,以保證一個(gè)項(xiàng)目可供使用。 當(dāng)線程完成該項(xiàng)目時(shí),它會(huì)返回到池中,并且將許可返回給信號量,允許另一個(gè)線程獲取該項(xiàng)目。 請注意,調(diào)用acquire時(shí)不會(huì)持有同步鎖,因?yàn)檫@會(huì)阻止項(xiàng)目返回到池中。 信號量封裝了限制訪問池所需的同步,與維護(hù)池本身一致性所需的任何同步分開。
- 初始化為 1 的信號量,并且使用時(shí)最多只有一個(gè)許可可用,可以用作互斥鎖。 這通常被稱為二進(jìn)制信號量,因?yàn)樗挥袃煞N狀態(tài):一種許可可用,或零許可可用。 以這種方式使用時(shí),二進(jìn)制信號量具有屬性(與許多java.util.concurrent.locks.Lock實(shí)現(xiàn)不同),即“鎖”可以由所有者以外的線程釋放(因?yàn)樾盘柫繘]有所有權(quán)的概念)。 這在某些特定上下文中很有用,例如死鎖恢復(fù)。
- 此類的構(gòu)造函數(shù)可以選擇接受公平參數(shù)。 當(dāng)設(shè)置為 false 時(shí),此類不保證線程獲取許可的順序。 當(dāng)公平性設(shè)置為真時(shí),信號量保證調(diào)用任何acquire方法的線程被選擇以按照它們對這些方法的調(diào)用的處理順序(先進(jìn)先出;FIFO)獲得許可。
- 通常,用于控制資源訪問的信號量應(yīng)初始化為公平的,以確保沒有線程因訪問資源而餓死。 當(dāng)使用信號量進(jìn)行其他類型的同步控制時(shí),非公平排序的吞吐量優(yōu)勢通常超過公平性考慮。
- 內(nèi)存一致性影響:在調(diào)用“釋放”方法(如release()之前線程中的操作發(fā)生在另一個(gè)線程中成功的“獲取”方法(如acquire()之后的操作之前。
簡單講述 | Phaser & Exchanger
Phaser
Phaser一種可重用的同步屏障,功能上類似于CyclicBarrier和CountDownLatch,但使用上更為靈活。非常適用于在多線程環(huán)境下同步協(xié)調(diào)分階段計(jì)算任務(wù)(Fork/Join框架中的子任務(wù)之間需同步時(shí),優(yōu)先使用Phaser)
//默認(rèn)的構(gòu)造方法,初始化注冊的線程數(shù)量為0,可以動(dòng)態(tài)注冊
Phaser();
//指定了線程數(shù)量的構(gòu)造方法
Phaser(int parties);
//添加一個(gè)注冊者 向此移相器添加一個(gè)新的未到達(dá)方。 如果正在進(jìn)行對onAdvance調(diào)用,則此方法可能會(huì)在返回之前等待其完成。
register();
//添加指定數(shù)量的注冊者 將給定數(shù)量的新未到達(dá)方添加到此移相器(移相器就是Phaser)。
bulkRegister(int parties);
// 到達(dá)屏障點(diǎn)直接執(zhí)行 無需等待其他人到達(dá)。
arrive();
//到達(dá)屏障點(diǎn)后,也必須等待其他所有注冊者到達(dá)這個(gè)屏障點(diǎn)才能繼續(xù)下一步
arriveAndAwaitAdvance();
//到達(dá)屏障點(diǎn),把自己注銷了,不用等待其他的注冊者到達(dá)
arriveAndDeregister();
//多個(gè)線程達(dá)到注冊點(diǎn)之后,會(huì)回調(diào)這個(gè)方法,可以做一些邏輯的補(bǔ)充
onAdvance(int phase, int registeredParties);
package com.crush.juc05;import java.util.concurrent.Phaser;public class PhaserDemo {private static Phaser phaser = new MyPhaser();//自定義一個(gè)移相器來自定義輸出static class MyPhaser extends Phaser {/*** @deprecated 在即將到來的階段提前時(shí)執(zhí)行操作并控制終止的可覆蓋方法。 此方法在推進(jìn)此移相器的一方到達(dá)時(shí)調(diào)用(當(dāng)所有其他等待方處于休眠狀態(tài)時(shí))。* 如果此方法返回true ,則此移相器將在提前時(shí)設(shè)置為最終終止?fàn)顟B(tài),并且對isTerminated后續(xù)調(diào)用將返回 true。* @param phase 進(jìn)入此方法的當(dāng)前階段號,在此移相器前進(jìn)之前* @param registeredParties 當(dāng)前注冊方的數(shù)量* @return*/@Overrideprotected boolean onAdvance(int phase, int registeredParties) {if (phase == 0) {System.out.println("所有人都到達(dá)了網(wǎng)吧,準(zhǔn)備開始開黑!!!");return false;} else if (phase == 1) {System.out.println("大家都同意,一起去次燒烤咯!!!");return false;} else if (phase == 2) {System.out.println("大家一起回寢室!!!");return true;}return true;}}//構(gòu)建一個(gè)線程任務(wù)static class DoSomeThing implements Runnable {@Overridepublic void run() {/*** 向此移相器添加一個(gè)新的未到達(dá)方*/phaser.register();System.out.println(Thread.currentThread().getName() + "從家里出發(fā),準(zhǔn)備去學(xué)校后街上網(wǎng)開黑!!!");phaser.arriveAndAwaitAdvance();System.out.println(Thread.currentThread().getName() + "上著上著餓了,說去次燒烤嗎?");phaser.arriveAndAwaitAdvance();System.out.println(Thread.currentThread().getName() + "燒烤次完了");phaser.arriveAndAwaitAdvance();}}public static void main(String[] args) throws Exception {DoSomeThing thing = new DoSomeThing();new Thread(thing, "小明").start();new Thread(thing, "小王").start();new Thread(thing, "小李").start();}
}
/*** 小李從家里出發(fā),準(zhǔn)備去學(xué)校后街上網(wǎng)開黑!!!* 小王從家里出發(fā),準(zhǔn)備去學(xué)校后街上網(wǎng)開黑!!!* 小明從家里出發(fā),準(zhǔn)備去學(xué)校后街上網(wǎng)開黑!!!* 所有人都到達(dá)了網(wǎng)吧,準(zhǔn)備開始開黑!!!* 小李上著上著餓了,說去次燒烤嗎?* 小明上著上著餓了,說去次燒烤嗎?* 小王上著上著餓了,說去次燒烤嗎?* 大家都同意,一起去次燒烤咯!!!* 小明燒烤次完了* 小李燒烤次完了* 小王燒烤次完了* 大家一起回寢室!!!*/
注意:這里只是做了簡單的一個(gè)使用,更深入的了解,我暫時(shí)也沒有,想要研究可以去查一查。
Exchanger
Exchanger允許兩個(gè)線程在某個(gè)匯合點(diǎn)交換對象,在某些管道設(shè)計(jì)時(shí)比較有用。
Exchanger提供了一個(gè)同步點(diǎn),在這個(gè)同步點(diǎn),一對線程可以交換數(shù)據(jù)。每個(gè)線程通過exchange()方法的入口提供數(shù)據(jù)給他的伙伴線程,并接收他的伙伴線程提供的數(shù)據(jù)并返回。
當(dāng)兩個(gè)線程通過Exchanger交換了對象,這個(gè)交換對于兩個(gè)線程來說都是安全的。Exchanger可以認(rèn)為是 SynchronousQueue 的雙向形式,在運(yùn)用到遺傳算法和管道設(shè)計(jì)的應(yīng)用中比較有用。
這個(gè)的使用我在Dubbo中的總體架構(gòu)圖中看到了它的身影。
十二、總結(jié)
本文主要介紹了JUC是什么、JUC框架結(jié)構(gòu)概述、常用到的類匯總、相關(guān)名詞、重點(diǎn)介紹了Lock鎖,Callable接口、Feature接口以及JUC三大常用工具類,希望對大家有幫助,歡迎評論區(qū)留言。