血洗多線程,抱得 offer 歸

工做和麪試之中,遇到了不少多線程問題。這裏我總結了一下,但願對你有所幫助。本篇內容,基本上都是一些反例,有些很低級但常見。html

固然,面試時拿來裝逼用,也是極好的。java

先來10個。面試

我來評個級

1、玩命的建立線程池

現象: 系統資源耗盡,進程僵死。vim

緣由: 每次方法執行,都new一個線程池。api

小姐姐味道解決方式:共用一個線程池便可。安全

做死等級: 五顆星bash

腦殘等級: 五顆星網絡

void doJob(){
    ThreadPoolExecutor exe = new ThreadPoolExecutor(...);
    exe.submit(new Runnable(){...})
}
複製代碼

2、鎖泄漏

現象: 某個線程一直持有鎖而不釋放,形成鎖泄漏。多線程

緣由: 未知異常或邏輯致使unlock函數未執行。併發

小姐姐味道解決方式:始終將unlock函數放在finally中。

做死等級: 三顆星

腦殘等級: 四顆星

private final Lock lock = new ReentrantLock();
void doJob(){
    try{
        lock.lock();
        //do. sth
        lock.unlock();
    }catch(Exception e){
    }
}
複製代碼

3、忘記同步變量

現象: 在某個條件下,拋出IllegalMonitorStateException。

緣由: 調用wait、notify等,忘記synchronized,或者同步了錯誤的變量。

小姐姐味道解決方式:調用這些函數以前,要使用同步關鍵字同步它。

做死等級: 兩顆星

腦殘等級: 四顆星

Object condition = new Object();

condition.wait();
複製代碼

4、HashMap死循環

現象: cpu佔用高,發生死循環,使用jstack查看是阻塞在get方法上。

緣由: 在某種條件下,進行rehash時,會造成環形鏈。某些get請求會走到這個環上。

小姐姐味道解決方式:多線程環境下,使用ConcurrentHashMap,別猶豫。

做死等級: 四顆星

腦殘等級: 四顆星

5、給同步的變量從新賦值

現象: 不可以達到同步效果,結果是錯誤的。

緣由: 非基本類型被從新賦值,會改變鎖的指向,不一樣線程持有的鎖可能不同。

小姐姐味道解決方式:把鎖對象聲明爲final類型。

做死等級: 四顆星

腦殘等級: 三顆星

List listeners = new ArrayList();

void add(Listener listener, boolean upsert){
    synchronized(listeners){
        List results = new ArrayList();
        for(Listener ler:listeners){
        ...
        }
        listeners = results;
    }
}
複製代碼

6、線程循環未捕獲異常

現象: 線程做業沒法繼續運行,不明終止。

緣由: 未捕獲循環中的異常,形成線程退出。

小姐姐味道解決方式:習慣性捕獲全部異常。

做死等級: 三顆星

腦殘等級: 三顆星

volatile boolean run = true;
void loop(){
    while(run){
        //do . sth
        int a = 1/0;
    }
}
複製代碼

7、volatile誤做計數器

現象: 多線程計數結果有誤。

緣由: volatile保證可見性,不保證原子性,多線程操做並不能保證其正確性。

小姐姐味道解決方式:直接使用Atomic類。

做死等級: 三顆星

腦殘等級: 兩顆星

volatile count = 0;
void add(){
    ++count;
}
複製代碼

8、錯誤保護範圍

現象: 雖然使用了線程安全的集合,但達不到同步效果。

緣由: 操做要修改多個線程安全的集合,但操做自己不是原子的。

小姐姐味道解決方式:弄明白要保護的代碼邏輯域。

做死等級: 三顆星

腦殘等級: 四顆星

private final ConcurrentHashMap<String,Integer> nameToNumber;
private final ConcurrentHashMap<Integer,Salary> numberToSalary;
public int geBonusFor(String name) {
    Integer serialNum = nameToNumber.get(name);
    Salary salary = numberToSalary.get(serialNum);
    return salary.getBonus();
}
複製代碼

再好比下面的錯誤代碼。

Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());

if(!map.containsKey("foo"))
    map.put("foo", "bar");
複製代碼

9、一些老的日期處理類

現象: 使用全局的Calendar,SimpleDateFormat等進行日期處理,發生異常或者數據不許確。

緣由: 這倆東西不是線程安全的,併發調用會有問題。

小姐姐味道解決方式:放在ThreadLocal中,建議使用線程安全的DateTimeFormatter。

做死等級: 三顆星

腦殘等級: 三顆星

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

Date dododo(String str){
    return format(str);
}
複製代碼

10、代碼死鎖

現象: 代碼產生死鎖和相互等待。

緣由: 代碼知足了下面四個條件:互斥;不可剝奪;請求和保持;循環等待。

小姐姐味道解決方式:破壞這四個條件。或者少用同步。

做死等級: 兩顆星

腦殘等級: 一顆星

下面是一段簡單的死鎖代碼。

final Object lock1 = new Object();
final Object lock2 = new Object();
new Thread(new Runnable() {
    @Override
    public void run() {
        sleep(1000);
        synchronized (lock1) {
            synchronized (lock2) {
            }
        }
    }
}).start();
new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized (lock2) {
            sleep(1000);
            synchronized (lock1) {
            }
        }
    }
}).start();
複製代碼

11、long變量讀取無效值

現象: 會讀取到非設置的值。

緣由: long變量讀寫不是原子的,可能會讀到1個變量的高32位和另外一個變量的低32位字節。

小姐姐味道解決方式:確保long和double變量的數據正確,能夠加上volatile關鍵字。

做死等級: 一顆星

腦殘等級: 沒有星

擴展閱讀(jdk10):docs.oracle.com/javase/spec…

咦?怎麼有11個?必定是多線程計算錯誤。

End

許多java開發,都是剛剛接觸多線程開發。但即便是有經驗的開發,也會陷入不少多線程的陷阱。當你的程序沒有得相應的指望,但願本文能幫你瞭解到其中的微妙之處。

多線程的使用是及其複雜的,使用低級api出錯的機率會成倍增長,對技能要求也較高。所幸,concurrent包使得這個過程方便了不少,但依然存在資源規劃和同步失效的問題。小姐姐味道這裏一個比較淺顯但全面的總結:JAVA多線程使用場景和注意事項簡版,但健壯的代碼還要靠你本身去實踐呀。

更多精彩文章。

《微服務不是所有,只是特定領域的子集》

《「分庫分表" ?選型和流程要慎重,不然會失控》

這麼多監控組件,總有一款適合你

《Linux生產環境上,最經常使用的一套「vim「技巧》

《使用Netty,咱們到底在開發些什麼?》

Linux五件套之類的。

《Linux之《荒島餘生》(一)準備篇》

《Linux之《荒島餘生》(二)CPU篇》

《Linux之《荒島餘生》(三)內存篇》

《Linux之《荒島餘生》(四)I/O篇》

《Linux之《荒島餘生》(五)網絡篇》

更多請關注。固然也能夠關注公衆號。

相關文章
相關標籤/搜索