java并發(fā)編程的藝術筆記第六章——java并發(fā)容器和框架

1谍咆、ConcurrentHashMap的實現(xiàn)原理與使用

1.1变隔、為什么使用ConcurrentHashMap

  • HashMap非線程安全
  • HashTable讀寫都需要加鎖哼转,效率低下
  • ConcurrentHashMap的鎖分段技術可以提高并發(fā)效率

1.2、ConcurrentHashMap的結構

ConcurrentHashMap由Segment數(shù)組結構和HashEntry數(shù)組結構組成,Segement是一種可重入鎖抖坪,在ConcurrentHashMap扮演著鎖的角色;HashEntry用于存儲鍵值對數(shù)據(jù)闷叉,一個ConcurrentHashMap中包含一個Segment數(shù)組擦俐,它是數(shù)組和鏈表結構。一個Segment里包含一個HashEntry數(shù)組握侧,當對HashEntry數(shù)組進行修改操作時必須要獲取它對應的Segment鎖蚯瞧。

1.3、ConcurrentHashMap的初始化

1.4品擎、定位Segment

通過散列算法定位Segment埋合,散列沖突

2、ConcurrentLinkedQueue

并發(fā)編程中實現(xiàn)線程安全的隊列有兩種方式萄传,一種是阻塞隊列甚颂,一種是非阻塞隊列,非阻塞的實現(xiàn)方式可以通過CAS方式來實現(xiàn)秀菱。

ConcurrentLinkedQueue是一個基于鏈接節(jié)點的無界安全隊列振诬。它采用先進先出的方式對節(jié)點進行排序,當我們添加一個元素的時候答朋,它會添加至隊列的隊尾贷揽,當我們獲取元素的時候,它會返回隊列頭部的元素梦碗。

3禽绪、java中的阻塞隊列

阻塞隊列支持阻塞的添加/移除元素的方法环壤。支持阻塞的插入的意思是:當隊列已滿時萨蚕,隊列會阻塞插入隊列的線程,直到隊列有空位稼锅;支持阻塞的移除的意思是:當隊列為空時斩例,隊列會阻塞移除隊列元素的線程雄人,直到隊列中有新的元素添加進來。

阻塞隊列場用于生產(chǎn)/消費者模式念赶,生產(chǎn)者是向隊列中添加元素的線程础钠,消費者是從隊列中獲取元素的線程,而阻塞隊列在其中充當著容器的角色叉谜。
阻塞隊列的插入和移除有四種操作方式旗吁,詳情請參考文檔。

java中有7中阻塞隊列停局,分別是:

  • ArrayBlockingQueue:一個由數(shù)組結構組成的有界阻塞隊列
  • LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列很钓。
  • PriorityBlockingQueue:一個支持優(yōu)先級排序的無界阻塞隊列香府。
  • DelayQueue:一個支持延時獲取元素的無界阻塞隊列。
  • SynchronousQueue:一個不存儲元素的阻塞隊列码倦。
  • LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列企孩。
  • LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

著重介紹下DelayQueue袁稽,它可以運用在以下業(yè)務場景:

  • 緩存系統(tǒng)的設置:可以用DelayQueue保存元素的有效期勿璃,使用一個線程無限循環(huán)查詢DelayQueue,一旦能從DelayQueue中獲取到元素推汽,則說明緩存有效期到了蝗柔。
  • 使用DelayQueue保存當天將會執(zhí)行的任務和時間,一旦從DelayQueue獲取到任務民泵,就開始執(zhí)行,TimeQueue就是使用DelayQueue來實現(xiàn)的槽畔。

在公司有這么一個業(yè)務場景:訂單支付后要給商戶發(fā)送相應的通知栈妆,針對同一條通知記錄,如果是第一次發(fā)厢钧,則需要等待的時間是0分鐘鳞尔,第二次發(fā)則需要等待1分鐘,第三次發(fā)則需要等待3分鐘早直,即發(fā)送次數(shù)每+1寥假,則需要等待的時長也要相應的增加,那使用DelayQueue就能很好的實現(xiàn)這個功能了霞扬。以下是參考實現(xiàn):

package main.java.com.robot.demo;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * @author: 會跳舞的機器人
 * @date: 2017/8/17 14:43
 * @description: 通知任務
 */
public class NotifyTask implements Delayed, Runnable {
    /**
     * 任務名稱
     */
    private String notifyTaskName;

    /**
     * 執(zhí)行時間糕韧,單位:毫秒
     */
    private long executeTime;

    public NotifyTask(String notifyTaskName, long executeTime) {
        this.notifyTaskName = notifyTaskName;
        this.executeTime = executeTime;
    }


    /**
     * 獲取延遲時間
     *
     * @param unit
     * @return 返回當前元素還需要延長多久時間
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(executeTime - System.currentTimeMillis(), unit.MILLISECONDS);
    }

    /**
     * 用來指定元素的順序,讓延時時間最長的放在隊列的末尾
     *
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        NotifyTask notifyTask = (NotifyTask) o;
        return executeTime > notifyTask.executeTime ? 1 : (executeTime < notifyTask.executeTime ? -1 : 0);
    }

    @Override
    public void run() {
        System.out.println("當前時間毫秒數(shù):" + System.currentTimeMillis() + ",線程:" + this.toString() + "正在執(zhí)行");

    }

    @Override
    public String toString() {
        return "NotifyTask{" +
                "notifyTaskName='" + notifyTaskName + '\'' +
                ", executeTime=" + executeTime +
                '}';
    }
}

測試:

package main.java.com.robot.demo;

import java.util.Random;
import java.util.concurrent.DelayQueue;

/**
 * @author: 會跳舞的機器人
 * @date: 2017/8/17 14:52
 * @description: 延遲隊列DelayQueue測試
 */
public class NotifyTaskTest {
    /**
     * 通知任務存放的延遲隊列
     */
    private static DelayQueue<NotifyTask> tasks = new DelayQueue<>();


    public static void main(String[] args) {
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            // 隨機產(chǎn)生一個秒數(shù)
            int seconds = random.nextInt(5);
            NotifyTask notifyTask = new NotifyTask("任務" + i, System.currentTimeMillis() + (seconds * 1000));
            tasks.put(notifyTask);
        }
        while (true) {
            NotifyTask notifyTask = tasks.poll();
            if (notifyTask != null) {
                notifyTask.run();
            }
            // 如果隊列中的元素全部被取完,則跳出循環(huán)
            if (tasks.size() == 0) {
                break;
            }
        }
    }
}

控制臺輸出:

當前時間毫秒數(shù):1502953649855喻圃,線程:NotifyTask{notifyTaskName='任務1', executeTime=1502953649855}正在執(zhí)行
當前時間毫秒數(shù):1502953649855萤彩,線程:NotifyTask{notifyTaskName='任務3', executeTime=1502953649855}正在執(zhí)行
當前時間毫秒數(shù):1502953650855,線程:NotifyTask{notifyTaskName='任務0', executeTime=1502953650855}正在執(zhí)行
當前時間毫秒數(shù):1502953651855斧拍,線程:NotifyTask{notifyTaskName='任務2', executeTime=1502953651855}正在執(zhí)行
當前時間毫秒數(shù):1502953651855雀扶,線程:NotifyTask{notifyTaskName='任務4', executeTime=1502953651855}正在執(zhí)行

從輸出中可以看出任務的執(zhí)行時間都是我們創(chuàng)建任務的時候指定的時間。

4肆汹、Fork/Join框架

4.1愚墓、什么是Fork/Join框架

Fork/Join框架是Java 7提供的一個用于并行執(zhí)行任務的框架,是一個把大任務分割成若干
個小任務昂勉,最終匯總每個小任務結果后得到大任務結果的框架浪册。

我們再通過Fork和Join這兩個單詞來理解一下Fork/Join框架。Fork就是把一個大任務切分
為若干子任務并行的執(zhí)行硼啤,Join就是合并這些子任務的執(zhí)行結果议经,最后得到這個大任務的結
果斧账。比如計算1+2+…+10000,可以分割成10個子任務煞肾,每個子任務分別對1000個數(shù)進行求和咧织,
最終匯總這10個子任務的結果。

4.2籍救、工作竊取算法

工作竊认熬睢(work-stealing)算法是指某個線程從其他隊列里竊取任務來執(zhí)行。那么蝙昙,為什么
需要使用工作竊取算法呢闪萄?假如我們需要做一個比較大的任務,可以把這個任務分割為若干
互不依賴的子任務奇颠,為了減少線程間的競爭败去,把這些子任務分別放到不同的隊列里,并為每個
隊列創(chuàng)建一個單獨的線程來執(zhí)行隊列里的任務烈拒,線程和隊列一一對應圆裕。比如A線程負責處理A
隊列里的任務。但是荆几,有的線程會先把自己隊列里的任務干完吓妆,而其他線程對應的隊列里還有
任務等待處理。干完活的線程與其等著吨铸,不如去幫其他線程干活行拢,于是它就去其他線程的隊列
里竊取一個任務來執(zhí)行。而在這時它們會訪問同一個隊列诞吱,所以為了減少竊取任務線程和被
竊取任務線程之間的競爭舟奠,通常會使用雙端隊列,被竊取任務線程永遠從雙端隊列的頭部拿
任務執(zhí)行房维,而竊取任務的線程永遠從雙端隊列的尾部拿任務執(zhí)行鸭栖。

工作竊取算法的優(yōu)點:充分利用線程進行并行計算,減少了線程間的競爭握巢。

工作竊取算法的缺點:在某些情況下還是存在競爭晕鹊,比如雙端隊列里只有一個任務時。并
且該算法會消耗了更多的系統(tǒng)資源暴浦,比如創(chuàng)建多個線程和多個雙端隊列溅话。

4.3、使用Fork/Join框架

讓我們通過一個簡單的需求來使用Fork/Join框架歌焦,需求是:計算1+2+3+4的結果飞几。

package main.java.com.robot.demo;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

/**
 * @author: 會跳舞的機器人
 * @date: 2017/8/17 15:45
 * @description: Fork/Join框架簡單demo
 */
public class CountTask extends RecursiveTask<Integer> {
    /**
     * 閾值
     */
    private static final int THRESHOLD = 2;

    private int start;

    private int end;

    public CountTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int sum = 0;
        // 判斷任務是否足夠小,足夠小就直接計算
        boolean canCompute = (end - start) <= THRESHOLD;
        if (canCompute) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            // 如果任務大于閾值独撇,就分解成兩個任務執(zhí)行
            int midel = (end - start) / 2;
            CountTask leftTask = new CountTask(start, midel);
            CountTask rightTask = new CountTask(midel + 1, end);
            // 執(zhí)行子任務
            leftTask.fork();
            rightTask.fork();
            // 等待計算結果屑墨,合并子任務
            int leftResult = leftTask.join();
            int rightResult = rightTask.join();
            sum = leftResult + rightResult;
        }
        return sum;
    }

    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        // 生成一個計算任務躁锁,負責計算1+2+3+4
        CountTask task = new CountTask(1, 4);
        // 執(zhí)行一個任務
        Future<Integer> result = forkJoinPool.submit(task);
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    }
}

4.4、Fork/Join框架設計及其實現(xiàn)原理

參考書本

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末卵史,一起剝皮案震驚了整個濱河市战转,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌以躯,老刑警劉巖槐秧,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異忧设,居然都是意外死亡刁标,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門址晕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膀懈,“玉大人,你說我怎么就攤上這事谨垃±羯埃” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵乘客,是天一觀的道長。 經(jīng)常有香客問我淀歇,道長易核,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任浪默,我火速辦了婚禮牡直,結果婚禮上,老公的妹妹穿的比我還像新娘纳决。我一直安慰自己碰逸,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布阔加。 她就那樣靜靜地躺著饵史,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胜榔。 梳的紋絲不亂的頭發(fā)上胳喷,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音夭织,去河邊找鬼吭露。 笑死,一個胖子當著我的面吹牛尊惰,可吹牛的內容都是我干的讲竿。 我是一名探鬼主播泥兰,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼题禀!你這毒婦竟也來了鞋诗?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤投剥,失蹤者是張志新(化名)和其女友劉穎师脂,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體江锨,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡吃警,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了啄育。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酌心。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挑豌,靈堂內的尸體忽然破棺而出安券,到底是詐尸還是另有隱情,我是刑警寧澤氓英,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布侯勉,位于F島的核電站,受9級特大地震影響铝阐,放射性物質發(fā)生泄漏址貌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一徘键、第九天 我趴在偏房一處隱蔽的房頂上張望练对。 院中可真熱鬧,春花似錦吹害、人聲如沸螟凭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽螺男。三九已至,卻和暖如春纵穿,著一層夾襖步出監(jiān)牢的瞬間烟号,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工政恍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留汪拥,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓篙耗,卻偏偏與公主長得像迫筑,于是被迫代替她去往敵國和親宪赶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容