溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java?current并發(fā)包怎么使用

發(fā)布時間:2023-02-22 17:46:40 來源:億速云 閱讀:125 作者:iii 欄目:開發(fā)技術

這篇文章主要介紹“Java current并發(fā)包怎么使用”,在日常操作中,相信很多人在Java current并發(fā)包怎么使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java current并發(fā)包怎么使用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

并發(fā)包

current并發(fā)包、在JDK1.5之前Java并沒有提供線程安全的一些工具類去操作多線程,需要開發(fā)人員自行編寫實現(xiàn)線程安全,但仍然無法完全避免低性能、死鎖、資源管理等問題。在JDK1.5時新增了java.util.current并發(fā)包,其中提供了許多供我們使用的并發(fā)編程工具類。

ConcurrentHashMap

Java集合框架提供了存儲容器HashMap用于存儲鍵值對,但是HashMap是線程不安全的。在并發(fā)編程中,我們向HashMap添加大量數(shù)據(jù)時,可能會出現(xiàn)各種預料之外的問題。

同時Java也提供了線程安全的集合類HashTable,打開HashTable的底層我們會發(fā)現(xiàn)HashTable的所有方法都利用synchtonized進行了上鎖機制來保證了線程安全,但是利用這種阻塞同步的機制來保證線程安全的同時會大大降低程序的性能和執(zhí)行效率,這也是為什么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先執(zhí)行完
        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);
        }
    }
}

如上述代碼所示,我們啟動兩條線程執(zhí)行同一任務:向容器中添加50萬條數(shù)據(jù),預期最終容器中的數(shù)據(jù)將會達到100萬條。

利用HashMap存儲時,發(fā)現(xiàn)程序會出現(xiàn)各種各樣的異常狀況

程序卡頓,不報異常也不停止

報異常

java.lang.ClassCastException: java.util.HashMap$Node cannot be cast to java.util.HashMap$TreeNode

最終產生錯誤數(shù)據(jù)

利用HashTable存儲時,發(fā)現(xiàn)HashTable可以準確存儲。并且對比HashTable和ConcurrentHashMap兩者的存儲速度,發(fā)現(xiàn)大差小不差甚至HashTable還要更快。那么為什么還要說HashTable效率低下呢?

是因為我們只是測試了對數(shù)據(jù)進行的寫操作,而沒有測試其他的像查詢、修改等操作。綜合來講ConcurrentHashMap的各項性能優(yōu)于HashTbale,所以我們在需要考慮線程安全時,就可以采用ConcurrentHashMap進行存儲數(shù)據(jù)

那么ConcurrentHashMap是如何既保證線程安全又不失高性能的存儲數(shù)據(jù)呢?

首先明確它的底層實現(xiàn)機制是用CAS機制+synchronized分段式鎖,屬于是悲觀和樂觀相結合

HashTable工作時會將整個哈希表進行上鎖,此時所有其他線程都將被阻塞,效率低下

Java?current并發(fā)包怎么使用

ConcurrentHashMap工作時利用synchronized進行分段式上鎖,我們知道哈希表底層基于數(shù)組實現(xiàn),數(shù)組中每個位置形成槽位以便后續(xù)成鏈或者轉換樹結構。而分段式上鎖就是將當前線程所存儲的該位置進行上鎖,其他位置仍可以被其他線程進行操作。

Java?current并發(fā)包怎么使用

CountDownLatch倒計數(shù)觸發(fā)

CountDownLatch同樣是current包下的一個同步工具,它的主要作用就是使當前線程等待一條或多條線程執(zhí)行完畢后再執(zhí)行當前線程。同時提供了兩個主要方法來控制線程的交替執(zhí)行

// 創(chuàng)建CountDownLatch
CountDownLatch cdl = new CountDownLatch(1);
cdl.await()// 讓出cpu,使當前線程等待
cdl.CountDown() // 計數(shù)器減1,只有當計數(shù)器為零時才會喚醒被await的線程

CountDownLatch提供了一個構造器用于參數(shù)Count,在創(chuàng)建時就給定計數(shù)個數(shù)。每次調用CountDown方法就減一知道減為0時才會執(zhí)行被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之后宰繼續(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");
        // 當前線程執(zhí)行完后倒計數(shù)減一
        count.countDown();
    }
}

但是有序線程執(zhí)行先后 順序不確定,也有可能打印出“B、A、C”

CyclicBarrier循環(huán)屏障

CyclicBarrier與CountDownLatch很容易弄混

CountDownLatch:使一條或多條線程等待其他線程執(zhí)行完畢之后再執(zhí)行自己,內部使用倒計數(shù),最終執(zhí)行被await等待的線程

CyclicBarrier:阻塞一個線程組,內部采用正計數(shù)。當被阻塞的線程達到某個數(shù)量時才能執(zhí)行指定的任務。我們每調用一次await代表阻塞了一條線程。

假設示例:五個人進入會議室執(zhí)行開會任務

// 六條線程:五個員工進入會議室、一個開會
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+"號員工",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());

我們創(chuàng)建了一個循環(huán)屏障用于控制線程執(zhí)行,當被await阻塞的線程數(shù)==5時將會執(zhí)行newMetting的Runnable線程任務

同時會發(fā)現(xiàn)最后一個到達會議室的人(線程)將會組織會議開始,這說明我們調用了await方法并不是將該線程阻塞。是由于CyclicBarrier底層由線程池實現(xiàn),每一條線程執(zhí)行完畢之后都會被線程池回收而不是阻塞

Semaphore指示燈

Semaphore用于設置一個或多個線程可以同時執(zhí)行即控制線程的并發(fā)數(shù)量,其他線程被阻塞。常用于限流操作。同時可以設置公平鎖和非公平鎖

Semaphore的使用與Lock工具有些類似,同樣是提供了兩個方法用于上鎖和解鎖。只是Semaphore可以自由的控制能拿到鎖的線程數(shù)

Semaphore提供了如下兩個構造器

public Semaphore(int permits) // permits為允許執(zhí)行的線程數(shù)
public Semaphore(int permits, boolean fair)
    // fair為true表示公平鎖,等待時間最長的線程將在下次進入 反之是不公平鎖

Semphore提供的兩個操作鎖方法

public void acquire()  // 表示獲得許可
public void release()  // 表示釋放許可

示例:

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 創(chuàng)建任務
        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);
        }
    }
}
// 抽離業(yè)務代碼
class Service{
    // 創(chuàng)建Semaphore對象 并指定線程數(shù)
    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()+"執(zhí)行成功");
        System.out.println(Thread.currentThread().getName()+"離開  時間:"+System.currentTimeMillis());
        // 釋放許可
        sp.release();
    }
}

如上述程序所示,我們在創(chuàng)建Semaphore時指定了允許的并發(fā)數(shù)量為2,那么業(yè)務代碼同時只能被兩個線程執(zhí)行,一旦一條線程執(zhí)行完畢之后將會釋放許可,立刻會有其他線程獲得許可進入執(zhí)行

Exchanger交換者

Exchanger用于線程間的通信、數(shù)據(jù)交換。Exchanger提供了一個同步點exchange方法:public V exchange(V x)互相交換數(shù)據(jù)的兩條線程必須都運行到了同步點才能執(zhí)行交換數(shù)據(jù)的操作,只有一方到達時就會進行等待,等待時間可以由開發(fā)人員設定

我們先來看下面的示例

public class ExchangerDemo {
    public static void main(String[] args) {
        // 創(chuàng)建交換者
        Exchanger<String> exchanger = new Exchanger<>();
        // 創(chuàng)建兩條線程進行交換數(shù)據(jù)
        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拿到數(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:"+"我是線程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ù)。這兩條線程擁有的是同一個交換者對象,所以可以實現(xiàn)數(shù)據(jù)交換。

前文提到過我們可以自定義線程等待的時間,就是再同步點exchange處等待另一條線程執(zhí)行到此的時間。利用exchange方法定義等待時間

public V exchange(V x, long timeout, TimeUnit unit)
    // timeout等待的時間數(shù)值  unit時間單位
    // 示例:只等待五秒
exchanger.exchange("111","5000", TimeUnit.SECONDS)

超出了規(guī)定的等待時間,正在等待的線程將被回收并拋出java.util.TimeoutException超時異常,所以交換數(shù)據(jù)的雙方必須都執(zhí)行到同步點才能進行數(shù)據(jù)交換。

到此,關于“Java current并發(fā)包怎么使用”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI