Java多線程(7):JUC(下)

来源:https://www.cnblogs.com/xiangwang1111/archive/2022/11/02/16849307.html
-Advertisement-
Play Games

您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~ 除了四種常見的同步器(發令槍、搖號器、柵欄和交換機),JUC還有所謂線程安全的容器、阻塞隊列和一些特殊的類。其中常出現的就是線程安全的容器和阻塞隊列。與其說這是兩個大的分類,還不如說它就是兩個用得最多的類:ConcurrentHashMap和A ...


您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~

 

除了四種常見的同步器(發令槍、搖號器、柵欄和交換機),JUC還有所謂線程安全的容器、阻塞隊列和一些特殊的類。其中常出現的就是線程安全的容器和阻塞隊列。與其說這是兩個大的分類,還不如說它就是兩個用得最多的類ConcurrentHashMapArrayBlockingQueue。

我的風格是儘量少講原理,多講實際生活中的案例,除非它非常重要,就像AQS,這玩意絕對是個重量級的神器,差不多整個JUC都是建立在它之上的。如果說學習多線程只有一次集中全部精力的機會的話,那我絕對建議把這份寶貴的精力花在AQS上,物超所值。至於搞明白ThreadPool底層原理、synchronized關鍵字原理,個人認為純屬浪費時間。因為即使弄明白了,那個機制你是改不了的,AQS原理弄明白了,可以直接拿來用,自己玩出花來。

從之前的集合類繼承結構圖可以知道ConcurrentHashMap繼承自Map,實現了ConcurrentMap介面,它是線程安全的Map,側重於放入或者獲取的速度,而不在乎順序。

因為現在的Java基本上都升級到了JDK1.8以上,有的甚至和官方保持同步,所以基本上很多JDK1.7及以下會出現的問題,在新的版本中都修複了,而且性能越來越好。所以只需要知道一點:在併發量比較高的環境中儘量使用ConcurrentHashMap就好了。不用弄明白為什麼,因為不值得(JDK1.8的ConcurrentHashMap底層是用紅黑樹實現的,至於什麼是紅黑樹,又得回頭補一些基礎,而且記住了也沒啥用)。當然如果是為了應付面試,稍稍瞭解一下就好了。

為了給大家直觀的印象ConcurrentHashMap怎麼個線程安全法,這裡那我之前的項目代碼做個演示(當然是刪除了很多實際需求代碼,僅演示ConcurrentHashMap)。

電商里為了造一些刷單數據,需要模擬真實用戶,那麼就由代碼產生好了:

/**
 * 隨機姓名產生器
 *
 * @author 湘王
 */
public class RandomInfo2 {
    private static final String firstname = "趙錢孫李周吳鄭王馮陳褚衛蔣沈韓楊朱秦尤許何呂施張孔曹嚴華金魏陶薑戚謝鄒喻水雲蘇潘" +
            "葛奚範彭郎魯韋昌馬苗鳳花方俞任袁柳鮑史唐費岑薛雷賀倪湯滕殷羅畢郝鄔安常樂於時傅卞齊康伍餘元卜顧孟平黃和穆蕭尹姚邵湛汪祁毛" +
            "禹狄米貝明臧計成戴宋茅龐熊紀舒屈項祝董粱杜阮席季麻強賈路婁危江童顏郭梅盛林刁鐘徐邱駱高夏蔡田胡凌霍萬柯盧莫房繆乾解應宗丁" +
            "宣鄧鬱單杭洪包諸左石崔吉龔程邢滑裴陸榮翁荀羊甄家封芮儲靳邴松井富烏焦巴弓牧隗山谷車侯伊寧仇祖武符劉景詹束龍葉幸司韶黎喬蒼" +
            "雙聞莘勞逄姬冉宰桂牛壽通邊燕冀尚農溫莊晏瞿茹習魚容向古戈終居衡步都耿滿弘國文東毆沃曾關紅游蓋益桓公晉楚閆";
    private static final String lastname = "偉剛勇毅俊秀娟英華慧巧美娜靜淑惠珠翠雅芝玉萍紅娥玲芬芳燕彩春菊蘭鳳潔梅琳素雲蓮真環" +
            "雪榮愛妹霞香月鶯媛艷瑞凡佳嘉瓊勤珍貞莉桂娣葉璧璐婭琦晶妍茜秋珊莎錦黛青倩婷姣婉嫻瑾穎露瑤怡嬋雁蓓紈儀荷丹蓉眉君琴蕊薇菁夢" +
            "嵐苑婕馨瑗琰韻融園藝詠卿聰瀾純毓悅昭冰爽琬茗羽希寧欣飄育瀅馥筠柔竹靄凝曉歡霄楓芸菲寒伊亞宜可姬舒影荔枝思麗峰強軍平保東文" +
            "輝力明永健世廣志義興良海山仁波寧貴福生龍元全國勝學祥才發武新利清飛彬富順信子傑濤昌成康星光天達安岩中茂進林有堅和彪博誠先" +
            "敬震振壯會思群豪心邦承樂紹功松善厚慶磊民友裕河哲江超浩亮政謙亨奇固之輪翰朗伯巨集言若鳴朋斌梁棟維啟克倫翔旭鵬澤晨辰士以建家" +
            "致樹炎德行時泰盛雄琛鈞冠策騰楠榕風航弘";

    /**
     * 隨機產生姓氏
     *
     */
    public static String getFirstName() {
        int strLen = firstname.length();
        int index = new Random().nextInt(strLen - 1);
        return firstname.substring(index, index + 1);
    }

    /**
     * 隨機產生名字
     *
     */
    public static String getLastName() {
        int strLen = lastname.length();
        int index = new Random().nextInt(strLen - 1);
        return lastname.substring(index, index + 2);
    }

    public static void count(final List<String> list, final Map<String, Integer> map) {
        long start = System.currentTimeMillis();
        // 這裡為了比較效率,特意調大了人數
        for (int i = 0; i < 10_000_000; i++) {
            String name = getFirstName() + getLastName();
            list.add(name);
        }
        System.out.println("產生了 " + list.size() + " 個名字");
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    list.stream().map(str -> str.substring(0, 1)).forEach(str -> {
                        Integer count = 1;
                        if (map.containsKey(str)) {
                            // 多線程情況下,普通的HashMap的get()方法得到的結果可能為null
                            // 但ConcurrentHashMap一定不會是null
                            count = map.get(str);
                            if (null == count) {
                                System.out.println("count == null");
                            }
                            ++count;
                        }
                        map.put(str, count);
                    });
                    System.out.println("總共 " + map.size() + " 個姓氏");
                }
            }).start();
        }
        int count = 0;
        for(Map.Entry<String, Integer> entry : map.entrySet()) {
            count = count + entry.getValue();
        }
        long end = System.currentTimeMillis();
        // 最後統計名字總數
        System.out.println("執行時間:" + (end - start) + " 毫秒");
    }

    public static void main(String[] args) throws IOException {
        List<String> list = new ArrayList<>();
        Map<String, Integer> map = new HashMap<>();
        count(list, map);

        list = new ArrayList<>();
        map = new ConcurrentHashMap<>();
        count(list, map);
    }
}

 

 

通過這個程式,可以非常直觀地看到這兩者的區別。至於原理,就不必強記了,因為你不可能記住,記住了也沒鳥用。

 

 

 

 

 

ArrayBlockingQueue和LinkedBlockingQueue在之前的線程池ThreadPool中也提過,它們的共同點都是一邊放一邊拿,唯一的不同是一個是數量有限的,一個是數量無限的。

 

 

 

之前拿上菜的案例演示了ArrayBlockingQueue,現在同樣的案例再用LinkedBlockingQueue來實現:

/**
 * LinkedBlockingQueue測試
 * 
 * @author 湘王
 */
public class LinkedBlockingQueueTester {
    public static BlockingQueue<String> queue = new LinkedBlockingQueue<String>(5);
    // 一個往裡放
    class Producer implements Runnable {
        @Override
        public void run() {
            try {
                queue.offer("川菜");
                System.out.println(Thread.currentThread().getName() + " 廚師做好 川菜");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    // 一個往外拿
    class Consumer implements Runnable {
        @Override
        public void run() {
            try {
                String food = queue.poll();
                System.out.println(Thread.currentThread().getName() + " 客人消費 " + food);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        // 和ArrayBlockingQueue不講究順序不同,LinkedBlockingQueue需要將生產的一方放在前面
        // 廚師做好菜
        for (int i = 0; i < 5; i++) {
            new Thread(new LinkedBlockingQueueTester().new Producer()).start();
        }
        // 客人等著菜
        for (int i = 0; i < 5; i++) {
            new Thread(new LinkedBlockingQueueTester().new Consumer()).start();
        }
    }
}

 

 

JUC中一些其他的類,例如ForkJoinPool,可以把任務分解成更小的任務,被分解出來任務會被繼續分解成更小的任務,更形象地說法是“分形器”。RecursiveTask是一種會返回結果的任務,它可以將自己分解成若幹更小的任務,並將這些任務的執行結果合併到一個結果里。這兩種用的不多。

整個JUC裡面最重要的還是前面3種共6Java類,掌握好AQS和這6個常見類,JUC基本就沒啥問題了。

 

 


 

 

感謝您的大駕光臨!咨詢技術、產品、運營和管理相關問題,請關註後留言。歡迎騷擾,不勝榮幸~

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Generator 非同步方案 相比於傳統回調函數的方式處理非同步調用,Promise最大的優勢就是可以鏈式調用解決回調嵌套的問題。但是這樣寫依然會有大量的回調函數,雖然他們之間沒有嵌套,但是還是沒有達到傳統同步代碼的可讀性。如果以下麵的方式寫非同步代碼,它是很簡潔,也更容易閱讀的。 // like sy ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一套既美觀又方便的後臺框架可以大大幅節約開發時間和成本,本文推薦 9 款漂亮、功能強大的後臺模板,本文推薦的開源項目已經收錄到 Awesome GitHub Repo。 Awesome GitHub Repo 是逛逛 GitHub 創建的 ...
  • 這幾天 程式員優雅哥 搭建了一個組件庫的基礎腳手架: vue3-component-library-archetype 在這個腳手架的基礎上,大家可以使用內置的 cli 快速創建新組件,按照套路開發組件及文檔即可。腳手架很大程度上簡化了環境的搭建、打包的配置、類型定義的抽取等工具,開箱即用,大... ...
  • 這部分內容是最近重新複習移動端,做頁面時的筆記,這是發佈的第一篇關於rem佈局實現。內容大致分為頁面實現過程中的重新複習到的不確信內容和未掌握內容,和在頁面實現中出現的問題和解決。 技術選型 方案:採用單頁面設計 技術:rem,媒體查詢,less 設計圖:750px 內容整理: *創建common. ...
  • 1.問題背景 項目在引用自研組件庫後,啟動後webpack報錯熱更新存在問題,無法正常啟動 2.解決方案 在詢問組件庫開發同事,被告知無問題;百度無果;查找webpack源碼後,發現能定位到報錯的代碼位置,卻無力解決時。我決定使用控制變數法,禁用熱更新插件,來解決問題。幸運的是,還真就解決了,註釋掉 ...
  • 一、前言:我全都要 面對當今前端界兩座大山一樣的主流框架,React和Vue,相信很多小伙伴都或多或少都產生過這樣疑問,而這樣的問題也往往很讓人頭疼和猶豫不決: 業務場景中是不是團隊用什麼我就用什麼? 如果選擇了其中一個使用,那為什麼不用另一個? 這兩個框架各有什麼優點和無法解決的問題? 最新版本的 ...
  • 本文主要記錄在使用 Vue 腳手架時,如何使用代理解決跨越問題,以及如何進行 Ajax 請求與後端伺服器進行數據交互。 ...
  • 前言 作為前端開發者,沒有購買雲伺服器的習慣,在只需要使用資料庫的情況下,開發微信小程式完全可以用現在免費的雲後臺。 常用的有微信自帶雲開發、leancloud,我常用的是leancloud,但接下來也想試試其他的好不好用、貴不貴。因此做了對比如下。僅涵蓋國內使用計價方案,Bmob沒有寄,只是換功能變數名稱 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...