天天看點

阻塞隊列、線程池、原子性及并發工具類

目錄

​​一、阻塞隊列​​

​​二、線程池​​

​​靜态方法建立線程池:​​

​​使用ThreadPoolexecutor類建立線程池:​​

​​三、原子性​​

​​四、并發工具類​​

​​HashTable類​​

​​ConcurrentHashMap類​​

​​CountDownLatch類​​

​​Semaphore類​​

一、阻塞隊列

ArrayBlockingQueue類:底層是數組,有界,沒有無參構造方法

LinkedBlockingQueue類:底層是連結清單,無界但最多能存放int的最大值,無參構造方法預設容量就是最大值

常用方法:

put(Object o):将參數放入隊列,如果放不進去會阻塞

take():取出第一個資料,取不到會阻塞

使用阻塞隊列實作生産者消費者模式:

public class Test {
    public static void main(String[] args) throws
            InterruptedException {
        ArrayBlockingQueue<String> arrayBlockingQueue = new
                ArrayBlockingQueue<>(1);
        Foodie foodie = new Foodie(arrayBlockingQueue);
        Cooker cooker = new Cooker(arrayBlockingQueue);
        foodie.start();
        cooker.start();
    }
}
class Foodie extends Thread{
    private ArrayBlockingQueue<String> arrayBlockingQueue;
    public Foodie(ArrayBlockingQueue<String> arrayBlockingQueue){
        this.arrayBlockingQueue = arrayBlockingQueue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                String take = arrayBlockingQueue.take();
                System.out.println("生産者消費了一個"+take);} catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Cooker extends Thread{
    private ArrayBlockingQueue<String> arrayBlockingQueue;
    public Cooker(ArrayBlockingQueue<String> arrayBlockingQueue){
        this.arrayBlockingQueue = arrayBlockingQueue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                arrayBlockingQueue.put("漢堡包");
                System.out.println("生産者放了一個漢堡包");} catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}      

二、線程池

每一個線程的啟動和結束都是比較消耗時間和資源的,如果在系統中用到很多線程,大量的線程啟動和結束操作會導緻性能變卡,響應變慢,為了解決這個問題,引入了線程池的設計思想,線程池就是一種生産者消費者模式

主要思想是建立若幹個線程放入池子,有任務需要處理時将任務送出到線程池中的任務隊列,任務處理完後線程池并不會銷毀,而是繼續線上程池中等待下一個任務

靜态方法建立線程池:

1、使用Executors類中的靜态方法static ExecutorService newCachedThreadPool()建立線程池,預設線程池是空的,根據需要建立線程,超過60秒未被使用的線程則銷毀,最多建立int最大值個線程

2、使用Executors類中的靜态方法static ExecutorService newFixedThreadPool(int nThreads)建立線程池,預設線程池是空的,根據需要建立線程,參數表示線程池最多能夠建立的線程,建立的線程将一直存到直到顯式調用shutdown()方法

3、這兩個方法傳回值類型是ExecutorService接口,這個接口裡邊定義了操作線程的方法,常用的兩個方法是:

submit(task):task是需要執行的任務,可以是實作Runnable接口或Callable接口的類對象,也可以是Lambda表達式

shutdown():用于任務執行後關閉線程池

使用ThreadPoolexecutor類建立線程池:

1、上述使用靜态方法建立的線程池實際上是使用類該類來建立并傳回線程池

2、常用構造方法:

public ThreadPoolExecutor(
        int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler
){}      

corePoolSize :核心線程數量

maximumPoolSize :最大線程數量

keepAliveTime :空閑線程存活時間的值

unit :存活時間的機關

workQueue :任務隊列

threadFactory :線程工廠,指定建立線程的方式

handler :任務拒絕政策,當任務隊列已滿,新任務不能送出到線程池時觸發對新任務

的處理政策

3、任務拒絕政策

ThreadPoolExecutor.AbortPolicy:丢棄任務并抛出RejectExecutionException異

常,預設的任務拒絕政策

ThreadPoolExecutor.DiscardPolicy:丢棄任務但不抛出異常,不推薦使用

ThreadPoolExecutor.DiscardOldestPolicy:抛棄隊列中等待最久的任務然後将當

前任務加入任務隊列

ThreadPoolExecutor.CallerRunsPolicy:調用任務的run()方法繞過線程池直接執行

三、原子性

原子性是指一個操作是不可中斷的,要麼全部執行成功要麼全部執行失敗。即使是多個線程一起執行,一個操作一旦開始,就不會被其他線程所幹擾。

volatile不能保證原子性,synchronized可以保證原子性。

原子性操作類,既能保證原子性又比synchronized高效,如Atomiclnteger類

1、構造方法

public AtomicInteger():建立初始值為0的對象

public AtomicInteger(int value):建立指定值的對對象

2、常用方法

方法名 說明
int get() 擷取值
int getAndIncrement() 以原子方式将目前值加1,傳回加1前的舊值
int incrementAndGet() 以原子方式将目前值加1,傳回加1後的新值
int addAndGet(int value) 以原子方式将目前值與參數相加,并傳回結果
int getAndSet(int value) 以原子方式将目前值設定為參數的值,傳回舊值

3、原理(底層使用自旋+CAS算法)

自旋:就是重新擷取共享變量的操作

CAS算法:

● 線程在修改共享資料時檢視共享資料的值與變量副本的值是否相同

● 如果相同說明共享變量的值沒有被其他線程修改,可以直接将新值賦給共享資料

● 如果不相同,說明在對變量副本進行操作時有其他線程修改了共享資料,此時不能

修改共享資料,而是重新擷取共享資料的值

4、sychronized與CAS的差別

相同點:在多線程的情況下,都可以保證共享資料的安全性

不同的:

1、sychronized總是從最壞的角度出發,認為每次擷取資料時,别的線程都有可能修改,是以每次操作共享資料前,都會上鎖(悲觀鎖)

2、CAS是從樂觀的角度出發,假設每次擷取資料時别的線程都不會修改,是以不上鎖,隻是在修改共享資料時再檢視其他線程有沒有修改共享資料,如果有就重新擷取新的共享資料,如果沒有就直接修改共享資料(樂觀鎖)

四、并發工具類

HashTable類

● HashMap是線程不安全的,為了保證資料安全性可以使用線程安全的HashTable代替

● HashTable效率比較低下

● HashTable采用sychronized悲觀鎖,當有線程通路時會将整個集合加鎖

ConcurrentHashMap類

● ConcurrentHashMap是線程安全的,效率較高

● 在JDK7和JDK8中實作的原理有差別

● JDK7原理

        ■ 使用無參構造建立對象時,建立一個預設長度16,加載因子為0.75的數組,數組名為

segment,并且這個數組無法擴容

        ■ 再建立長度為2的小數組,将該小數組位址存入segment數組的0索引,segment其他索引均為null,這個小數組作為模闆數組

        ■ 在添加元素時會根據元素的哈希值計算出在segment的應存入位置的索引。如果為null

則按照模闆數組建立小數組,建立完畢後會進行二次哈希,計算出在小數組中應存入位置的索引,然後直接存入;如果不是null則直接找到小數組進行二次哈希,計算出在小數組中應存入位置的索引,如果小數組需要擴容則擴容到兩倍,然後存入,如果小數組不需要擴容就檢視該位置有無元素,如果沒有元素直接存,如果有元素就調用equals()方法比較,相同的話不存,不相同就形成哈希桶結構

        ■ 根據添加的原理,該集合實際上是建立了16個哈希表結構

        ■ 保證線程安全的方式是,當線程對segment某個索引處的哈希表進行操作時對該索引處加鎖,而其他索引則不加鎖

● JDK8原理

CountDownLatch類

Semaphore類