您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~ 除了四種常見的同步器(發令槍、搖號器、柵欄和交換機),JUC還有所謂線程安全的容器、阻塞隊列和一些特殊的類。其中常出現的就是線程安全的容器和阻塞隊列。與其說這是兩個大的分類,還不如說它就是兩個用得最多的類:ConcurrentHashMap和A ...
您好,我是湘王,這是我的博客園,歡迎您來,歡迎您再來~
除了四種常見的同步器(發令槍、搖號器、柵欄和交換機),JUC還有所謂線程安全的容器、阻塞隊列和一些特殊的類。其中常出現的就是線程安全的容器和阻塞隊列。與其說這是兩個大的分類,還不如說它就是兩個用得最多的類:ConcurrentHashMap和ArrayBlockingQueue。
我的風格是儘量少講原理,多講實際生活中的案例,除非它非常重要,就像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種共6個Java類,掌握好AQS和這6個常見類,JUC基本就沒啥問題了。
感謝您的大駕光臨!咨詢技術、產品、運營和管理相關問題,請關註後留言。歡迎騷擾,不勝榮幸~