Java current并發(fā)包超詳細(xì)分析
并發(fā)包
current并發(fā)包、在JDK1.5之前Java并沒(méi)有提供線程安全的一些工具類去操作多線程,需要開發(fā)人員自行編寫實(shí)現(xiàn)線程安全,但仍然無(wú)法完全避免低性能、死鎖、資源管理等問(wèn)題。在JDK1.5時(shí)新增了java.util.current
并發(fā)包,其中提供了許多供我們使用的并發(fā)編程工具類。本文對(duì)于典型的并發(fā)包做出講解
ConcurrentHashMap
Java集合框架提供了存儲(chǔ)容器HashMap用于存儲(chǔ)鍵值對(duì),但是HashMap是線程不安全的。在并發(fā)編程中,我們向HashMap添加大量數(shù)據(jù)時(shí),可能會(huì)出現(xiàn)各種預(yù)料之外的問(wèn)題。
同時(shí)Java也提供了線程安全的集合類HashTable,打開HashTable的底層我們會(huì)發(fā)現(xiàn)HashTable的所有方法都利用synchtonized進(jìn)行了上鎖機(jī)制來(lái)保證了線程安全,但是利用這種阻塞同步的機(jī)制來(lái)保證線程安全的同時(shí)會(huì)大大降低程序的性能和執(zhí)行效率,這也是為什么HashTable被淘汰的原因
在JDK1.5之后Java就提供了保證性能高效、線程安全的鍵值對(duì)存儲(chǔ)容器ConcurrentHashMap
下面我們看下HashMap、HashTable、ConcurrentHashMap的對(duì)比
public class Demo01 { //public static Map<String,String> maps = new HashMap<String, String>(); //public static Map<String,String> maps = new Hashtable<String, String>(); public static Map<String,String> maps = new ConcurrentHashMap<String, String>(); public static void main(String[] args) throws Exception { Runnable task = new Temp(); Thread t1 = new Thread(task,"A線程"); Thread t2 = new Thread(task,"B線程"); t1.start(); t2.start(); // 保證t1和t2先執(zhí)行完 t1.join(); t2.join(); System.out.println("最終集合長(zhǎng)度:"+maps.size()); } } class Temp implements Runnable{ @Override public void run() { for (int i = 0; i < 500000; i++) { Demo01.maps.put(Thread.currentThread().getName()+i,Thread.currentThread().getName()+i); } } }
如上述代碼所示,我們啟動(dòng)兩條線程執(zhí)行同一任務(wù):向容器中添加50萬(wàn)條數(shù)據(jù),預(yù)期最終容器中的數(shù)據(jù)將會(huì)達(dá)到100萬(wàn)條。
利用HashMap存儲(chǔ)時(shí),發(fā)現(xiàn)程序會(huì)出現(xiàn)各種各樣的異常狀況
程序卡頓,不報(bào)異常也不停止
報(bào)異常
java.lang.ClassCastException: java.util.HashMap$Node cannot be cast to java.util.HashMap$TreeNode
最終產(chǎn)生錯(cuò)誤數(shù)據(jù)
利用HashTable存儲(chǔ)時(shí),發(fā)現(xiàn)HashTable可以準(zhǔn)確存儲(chǔ)。并且對(duì)比HashTable和ConcurrentHashMap兩者的存儲(chǔ)速度,發(fā)現(xiàn)大差小不差甚至HashTable還要更快。那么為什么還要說(shuō)HashTable效率低下呢?
是因?yàn)槲覀冎皇菧y(cè)試了對(duì)數(shù)據(jù)進(jìn)行的寫操作,而沒(méi)有測(cè)試其他的像查詢、修改等操作。綜合來(lái)講ConcurrentHashMap的各項(xiàng)性能優(yōu)于HashTbale,所以我們?cè)谛枰紤]線程安全時(shí),就可以采用ConcurrentHashMap進(jìn)行存儲(chǔ)數(shù)據(jù)
那么ConcurrentHashMap是如何既保證線程安全又不失高性能的存儲(chǔ)數(shù)據(jù)呢?
首先明確它的底層實(shí)現(xiàn)機(jī)制是用CAS機(jī)制+synchronized分段式鎖,屬于是悲觀和樂(lè)觀相結(jié)合
HashTable工作時(shí)會(huì)將整個(gè)哈希表進(jìn)行上鎖,此時(shí)所有其他線程都將被阻塞,效率低下
ConcurrentHashMap工作時(shí)利用synchronized進(jìn)行分段式上鎖,我們知道哈希表底層基于數(shù)組實(shí)現(xiàn),數(shù)組中每個(gè)位置形成槽位以便后續(xù)成鏈或者轉(zhuǎn)換樹結(jié)構(gòu)。而分段式上鎖就是將當(dāng)前線程所存儲(chǔ)的該位置進(jìn)行上鎖,其他位置仍可以被其他線程進(jìn)行操作。
CountDownLatch倒計(jì)數(shù)觸發(fā)
CountDownLatch同樣是current包下的一個(gè)同步工具,它的主要作用就是使當(dāng)前線程等待一條或多條線程執(zhí)行完畢后再執(zhí)行當(dāng)前線程。同時(shí)提供了兩個(gè)主要方法來(lái)控制線程的交替執(zhí)行
// 創(chuàng)建CountDownLatch CountDownLatch cdl = new CountDownLatch(1); cdl.await()// 讓出cpu,使當(dāng)前線程等待 cdl.CountDown() // 計(jì)數(shù)器減1,只有當(dāng)計(jì)數(shù)器為零時(shí)才會(huì)喚醒被await的線程
CountDownLatch提供了一個(gè)構(gòu)造器用于參數(shù)Count,在創(chuàng)建時(shí)就給定計(jì)數(shù)個(gè)數(shù)。每次調(diào)用CountDown方法就減一知道減為0時(shí)才會(huì)執(zhí)行被await等待的線程。
我們來(lái)看下面這個(gè)示例,目的是順序打印出“A、B、C”
public class Demo02 { public static void main(String[] args) { CountDownLatch count = new CountDownLatch(1); new ThreadA(count).start(); new ThreadB(count).start(); } } class ThreadA extends Thread{ private CountDownLatch count; public ThreadA(CountDownLatch count) { this.count = count; } @Override public void run() { System.out.println("A"); // 使當(dāng)前線程等待 等待打印B之后宰繼續(xù)執(zhí)行打印A try { count.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("C"); } } class ThreadB extends Thread{ private CountDownLatch count; public ThreadB(CountDownLatch count) { this.count = count; } @Override public void run() { System.out.println("B"); // 當(dāng)前線程執(zhí)行完后倒計(jì)數(shù)減一 count.countDown(); } }
但是有序線程執(zhí)行先后 順序不確定,也有可能打印出“B、A、C”
CyclicBarrier循環(huán)屏障
CyclicBarrier與CountDownLatch很容易弄混
CountDownLatch:使一條或多條線程等待其他線程執(zhí)行完畢之后再執(zhí)行自己,內(nèi)部使用倒計(jì)數(shù),最終執(zhí)行被await等待的線程
CyclicBarrier:阻塞一個(gè)線程組,內(nèi)部采用正計(jì)數(shù)。當(dāng)被阻塞的線程達(dá)到某個(gè)數(shù)量時(shí)才能執(zhí)行指定的任務(wù)。我們每調(diào)用一次await代表阻塞了一條線程。
假設(shè)示例:五個(gè)人進(jìn)入會(huì)議室執(zhí)行開會(huì)任務(wù)
// 六條線程:五個(gè)員工進(jìn)入會(huì)議室、一個(gè)開會(huì) public class CyclicBarrierDemo { public static void main(String[] args) { // 創(chuàng)建循環(huán)屏障 CyclicBarrier cb = new CyclicBarrier(5,new Metting()); for (int i = 1; i <= 4; i++) { new Employee(i+"號(hào)員工",cb).start(); } } } class Employee extends Thread{ private CyclicBarrier cb; public Employee(String s, CyclicBarrier cb) { super(s); this.cb = cb; } @Override public void run() { System.out.println(Thread.currentThread().getName()+"進(jìn)入會(huì)議室"); try { Thread.sleep(1000); cb.await(); } catch (Exception e) { e.printStackTrace(); } } } class Metting implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"組織會(huì)議,會(huì)議開始"); } }
上述代碼所示:
CyclicBarrier cb = new CyclicBarrier(5,new Metting());
我們創(chuàng)建了一個(gè)循環(huán)屏障用于控制線程執(zhí)行,當(dāng)被await阻塞的線程數(shù)==5時(shí)將會(huì)執(zhí)行newMetting的Runnable線程任務(wù)
同時(shí)會(huì)發(fā)現(xiàn)最后一個(gè)到達(dá)會(huì)議室的人(線程)將會(huì)組織會(huì)議開始,這說(shuō)明我們調(diào)用了await方法并不是將該線程阻塞。是由于CyclicBarrier底層由線程池實(shí)現(xiàn),每一條線程執(zhí)行完畢之后都會(huì)被線程池回收而不是阻塞
Semaphore指示燈
Semaphore用于設(shè)置一個(gè)或多個(gè)線程可以同時(shí)執(zhí)行即控制線程的并發(fā)數(shù)量,其他線程被阻塞。常用于限流操作。同時(shí)可以設(shè)置公平鎖和非公平鎖
Semaphore的使用與Lock工具有些類似,同樣是提供了兩個(gè)方法用于上鎖和解鎖。只是Semaphore可以自由的控制能拿到鎖的線程數(shù)
Semaphore提供了如下兩個(gè)構(gòu)造器
public Semaphore(int permits) // permits為允許執(zhí)行的線程數(shù) public Semaphore(int permits, boolean fair) // fair為true表示公平鎖,等待時(shí)間最長(zhǎng)的線程將在下次進(jìn)入 反之是不公平鎖
Semphore提供的兩個(gè)操作鎖方法
public void acquire() // 表示獲得許可 public void release() // 表示釋放許可
示例:
public class SemaphoreDemo { public static void main(String[] args) { // 創(chuàng)建任務(wù) Service service = new Service(); for (int i = 1; i <= 5; i++) { new MyThread(i+"號(hào)線程",service).start(); } } } // 線程類 class MyThread extends Thread{ private Service service; public MyThread(String name,Service service){ super(name); this.service = service; } @Override public void run() { try { service.testMethod(); } catch (Exception e) { throw new RuntimeException(e); } } } // 抽離業(yè)務(wù)代碼 class Service{ // 創(chuàng)建Semaphore對(duì)象 并指定線程數(shù) private Semaphore sp = new Semaphore(2); public void testMethod() throws Exception { // 獲取許可 sp.acquire(); System.out.println(Thread.currentThread().getName()+"進(jìn)入 時(shí)間:"+System.currentTimeMillis()); Thread.sleep(200); System.out.println(Thread.currentThread().getName()+"執(zhí)行成功"); System.out.println(Thread.currentThread().getName()+"離開 時(shí)間:"+System.currentTimeMillis()); // 釋放許可 sp.release(); } }
如上述程序所示,我們?cè)趧?chuàng)建Semaphore時(shí)指定了允許的并發(fā)數(shù)量為2,那么業(yè)務(wù)代碼同時(shí)只能被兩個(gè)線程執(zhí)行,一旦一條線程執(zhí)行完畢之后將會(huì)釋放許可,立刻會(huì)有其他線程獲得許可進(jìn)入執(zhí)行
Exchanger交換者
Exchanger用于線程間的通信、數(shù)據(jù)交換。Exchanger提供了一個(gè)同步點(diǎn)exchange方法:public V exchange(V x)
互相交換數(shù)據(jù)的兩條線程必須都運(yùn)行到了同步點(diǎn)才能執(zhí)行交換數(shù)據(jù)的操作,只有一方到達(dá)時(shí)就會(huì)進(jìn)行等待,等待時(shí)間可以由開發(fā)人員設(shè)定
我們先來(lái)看下面的示例
public class ExchangerDemo { public static void main(String[] args) { // 創(chuàng)建交換者 Exchanger<String> exchanger = new Exchanger<>(); // 創(chuàng)建兩條線程進(jìn)行交換數(shù)據(jù) new ThreadN("線程N(yùn)",exchanger).start(); new ThreadP("線程P",exchanger).start(); } } class ThreadN extends Thread{ private Exchanger<String> exchanger; public ThreadN(String name,Exchanger<String> exchanger) { super(name); this.exchanger = exchanger; } @Override public void run() { System.out.println(Thread.currentThread().getName()+"給線程P:"+"我是線程N(yùn)"); try { String exchange = exchanger.exchange("我是線程N(yùn)"); System.out.println("線程N(yùn)拿到數(shù)據(jù):"+exchange); } catch (InterruptedException e) { throw new RuntimeException(e); } } } class ThreadP extends Thread{ private Exchanger<String> exchanger; public ThreadP(String name,Exchanger<String> exchanger) { super(name); this.exchanger = exchanger; } @Override public void run() { System.out.println(Thread.currentThread().getName()+"給線程N(yùn):"+"我是線程P"); try { String exchange = exchanger.exchange("我是線程P"); System.out.println("線程P拿到數(shù)據(jù):"+exchange); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
根據(jù)最終打印,可以發(fā)現(xiàn)兩者交換了數(shù)據(jù)。這兩條線程擁有的是同一個(gè)交換者對(duì)象,所以可以實(shí)現(xiàn)數(shù)據(jù)交換。
前文提到過(guò)我們可以自定義線程等待的時(shí)間,就是再同步點(diǎn)exchange處等待另一條線程執(zhí)行到此的時(shí)間。利用exchange方法定義等待時(shí)間
public V exchange(V x, long timeout, TimeUnit unit) // timeout等待的時(shí)間數(shù)值 unit時(shí)間單位 // 示例:只等待五秒 exchanger.exchange("111","5000", TimeUnit.SECONDS)
超出了規(guī)定的等待時(shí)間,正在等待的線程將被回收并拋出java.util.TimeoutException
超時(shí)異常,所以交換數(shù)據(jù)的雙方必須都執(zhí)行到同步點(diǎn)才能進(jìn)行數(shù)據(jù)交換
到此這篇關(guān)于Java current并發(fā)包超詳細(xì)分析的文章就介紹到這了,更多相關(guān)Java current并發(fā)包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot之bootstrap和application的區(qū)別解讀
這篇文章主要介紹了SpringBoot之bootstrap和application的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Java class文件格式之屬性詳解_動(dòng)力節(jié)點(diǎn)java學(xué)院整理
這篇文章主要介紹了Java class文件格式之屬性詳解,需要的朋友可以參考下2017-06-06關(guān)于logback.xml和logback-spring.xml的區(qū)別及說(shuō)明
這篇文章主要介紹了關(guān)于logback.xml和logback-spring.xml的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06詳解Spring Boot 2.0.2+Ajax解決跨域請(qǐng)求的問(wèn)題
這篇文章主要介紹了詳解Spring Boot 2.0.2+Ajax解決跨域請(qǐng)求的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03java jackson 將對(duì)象轉(zhuǎn)json時(shí),忽略子對(duì)象的某個(gè)屬性操作
這篇文章主要介紹了java jackson 將對(duì)象轉(zhuǎn)json時(shí),忽略子對(duì)象的某個(gè)屬性操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10Springboot?jpa使用sum()函數(shù)返回結(jié)果如何被接收
這篇文章主要介紹了Springboot?jpa使用sum()函數(shù)返回結(jié)果如何接收,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02