您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java current并發包怎么使用”,在日常操作中,相信很多人在Java current并發包怎么使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java current并發包怎么使用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
current并發包、在JDK1.5之前Java并沒有提供線程安全的一些工具類去操作多線程,需要開發人員自行編寫實現線程安全,但仍然無法完全避免低性能、死鎖、資源管理等問題。在JDK1.5時新增了java.util.current
并發包,其中提供了許多供我們使用的并發編程工具類。
Java集合框架提供了存儲容器HashMap用于存儲鍵值對,但是HashMap是線程不安全的。在并發編程中,我們向HashMap添加大量數據時,可能會出現各種預料之外的問題。
同時Java也提供了線程安全的集合類HashTable,打開HashTable的底層我們會發現HashTable的所有方法都利用synchtonized進行了上鎖機制來保證了線程安全,但是利用這種阻塞同步的機制來保證線程安全的同時會大大降低程序的性能和執行效率,這也是為什么HashTable被淘汰的原因
在JDK1.5之后Java就提供了保證性能高效、線程安全的鍵值對存儲容器ConcurrentHashMap
下面我們看下HashMap、HashTable、ConcurrentHashMap的對比
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先執行完 t1.join(); t2.join(); System.out.println("最終集合長度:"+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); } } }
如上述代碼所示,我們啟動兩條線程執行同一任務:向容器中添加50萬條數據,預期最終容器中的數據將會達到100萬條。
利用HashMap存儲時,發現程序會出現各種各樣的異常狀況
程序卡頓,不報異常也不停止
報異常
java.lang.ClassCastException: java.util.HashMap$Node cannot be cast to java.util.HashMap$TreeNode
最終產生錯誤數據
利用HashTable存儲時,發現HashTable可以準確存儲。并且對比HashTable和ConcurrentHashMap兩者的存儲速度,發現大差小不差甚至HashTable還要更快。那么為什么還要說HashTable效率低下呢?
是因為我們只是測試了對數據進行的寫操作,而沒有測試其他的像查詢、修改等操作。綜合來講ConcurrentHashMap的各項性能優于HashTbale,所以我們在需要考慮線程安全時,就可以采用ConcurrentHashMap進行存儲數據
那么ConcurrentHashMap是如何既保證線程安全又不失高性能的存儲數據呢?
首先明確它的底層實現機制是用CAS機制+synchronized分段式鎖,屬于是悲觀和樂觀相結合
HashTable工作時會將整個哈希表進行上鎖,此時所有其他線程都將被阻塞,效率低下
ConcurrentHashMap工作時利用synchronized進行分段式上鎖,我們知道哈希表底層基于數組實現,數組中每個位置形成槽位以便后續成鏈或者轉換樹結構。而分段式上鎖就是將當前線程所存儲的該位置進行上鎖,其他位置仍可以被其他線程進行操作。
CountDownLatch同樣是current包下的一個同步工具,它的主要作用就是使當前線程等待一條或多條線程執行完畢后再執行當前線程。同時提供了兩個主要方法來控制線程的交替執行
// 創建CountDownLatch CountDownLatch cdl = new CountDownLatch(1); cdl.await()// 讓出cpu,使當前線程等待 cdl.CountDown() // 計數器減1,只有當計數器為零時才會喚醒被await的線程
CountDownLatch提供了一個構造器用于參數Count,在創建時就給定計數個數。每次調用CountDown方法就減一知道減為0時才會執行被await等待的線程。
我們來看下面這個示例,目的是順序打印出“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"); // 使當前線程等待 等待打印B之后宰繼續執行打印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"); // 當前線程執行完后倒計數減一 count.countDown(); } }
但是有序線程執行先后 順序不確定,也有可能打印出“B、A、C”
CyclicBarrier與CountDownLatch很容易弄混
CountDownLatch:使一條或多條線程等待其他線程執行完畢之后再執行自己,內部使用倒計數,最終執行被await等待的線程
CyclicBarrier:阻塞一個線程組,內部采用正計數。當被阻塞的線程達到某個數量時才能執行指定的任務。我們每調用一次await代表阻塞了一條線程。
假設示例:五個人進入會議室執行開會任務
// 六條線程:五個員工進入會議室、一個開會 public class CyclicBarrierDemo { public static void main(String[] args) { // 創建循環屏障 CyclicBarrier cb = new CyclicBarrier(5,new Metting()); for (int i = 1; i <= 4; i++) { new Employee(i+"號員工",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()+"進入會議室"); 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()+"組織會議,會議開始"); } }
上述代碼所示:
CyclicBarrier cb = new CyclicBarrier(5,new Metting());
我們創建了一個循環屏障用于控制線程執行,當被await阻塞的線程數==5時將會執行newMetting的Runnable線程任務
同時會發現最后一個到達會議室的人(線程)將會組織會議開始,這說明我們調用了await方法并不是將該線程阻塞。是由于CyclicBarrier底層由線程池實現,每一條線程執行完畢之后都會被線程池回收而不是阻塞
Semaphore用于設置一個或多個線程可以同時執行即控制線程的并發數量,其他線程被阻塞。常用于限流操作。同時可以設置公平鎖和非公平鎖
Semaphore的使用與Lock工具有些類似,同樣是提供了兩個方法用于上鎖和解鎖。只是Semaphore可以自由的控制能拿到鎖的線程數
Semaphore提供了如下兩個構造器
public Semaphore(int permits) // permits為允許執行的線程數 public Semaphore(int permits, boolean fair) // fair為true表示公平鎖,等待時間最長的線程將在下次進入 反之是不公平鎖
Semphore提供的兩個操作鎖方法
public void acquire() // 表示獲得許可 public void release() // 表示釋放許可
示例:
public class SemaphoreDemo { public static void main(String[] args) { // 創建任務 Service service = new Service(); for (int i = 1; i <= 5; i++) { new MyThread(i+"號線程",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); } } } // 抽離業務代碼 class Service{ // 創建Semaphore對象 并指定線程數 private Semaphore sp = new Semaphore(2); public void testMethod() throws Exception { // 獲取許可 sp.acquire(); System.out.println(Thread.currentThread().getName()+"進入 時間:"+System.currentTimeMillis()); Thread.sleep(200); System.out.println(Thread.currentThread().getName()+"執行成功"); System.out.println(Thread.currentThread().getName()+"離開 時間:"+System.currentTimeMillis()); // 釋放許可 sp.release(); } }
如上述程序所示,我們在創建Semaphore時指定了允許的并發數量為2,那么業務代碼同時只能被兩個線程執行,一旦一條線程執行完畢之后將會釋放許可,立刻會有其他線程獲得許可進入執行
Exchanger用于線程間的通信、數據交換。Exchanger提供了一個同步點exchange方法:public V exchange(V x)
互相交換數據的兩條線程必須都運行到了同步點才能執行交換數據的操作,只有一方到達時就會進行等待,等待時間可以由開發人員設定
我們先來看下面的示例
public class ExchangerDemo { public static void main(String[] args) { // 創建交換者 Exchanger<String> exchanger = new Exchanger<>(); // 創建兩條線程進行交換數據 new ThreadN("線程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"); try { String exchange = exchanger.exchange("我是線程N"); System.out.println("線程N拿到數據:"+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:"+"我是線程P"); try { String exchange = exchanger.exchange("我是線程P"); System.out.println("線程P拿到數據:"+exchange); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
根據最終打印,可以發現兩者交換了數據。這兩條線程擁有的是同一個交換者對象,所以可以實現數據交換。
前文提到過我們可以自定義線程等待的時間,就是再同步點exchange處等待另一條線程執行到此的時間。利用exchange方法定義等待時間
public V exchange(V x, long timeout, TimeUnit unit) // timeout等待的時間數值 unit時間單位 // 示例:只等待五秒 exchanger.exchange("111","5000", TimeUnit.SECONDS)
超出了規定的等待時間,正在等待的線程將被回收并拋出java.util.TimeoutException
超時異常,所以交換數據的雙方必須都執行到同步點才能進行數據交換。
到此,關于“Java current并發包怎么使用”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。