天天看點

正确使用鎖保護共享資料,協調異步線程(上)案例:團建避免濫用鎖鎖的用法

JMQ為提升性能,使用近乎無鎖的設計:

  1. MQ中的鎖是個必須使用的技術
  2. 使用鎖會降低系統性能

如何正确使用鎖?

異步和并發設計可大幅提升性能,但程式更複雜:

多線程

執行時,充斥不确定性。對一些需并發讀寫的共享資料,一着不慎滿盤皆輸。

案例:團建

老闆說:“部門準備團建,願意參加的回消息報名,統計下人數。都按我規定格式報名。”

老闆發了:“A,1人”。這時候B和C都要報名,過一會兒,他倆幾乎同時各發了一條消息,“B,2人”“C,2人”,每個人發的消息都隻統計了老闆和他們自己,一共2人,而這時,其實已經有3個人報名了,并且,在最後發消息的C的名單中,B的報名被覆寫。

典型并發讀寫導緻的資料錯誤。使用鎖可有效解決:任何時間都隻能有一個線程持鎖,持鎖線程才能通路被鎖保護的資源。

團建案例中,可認為群中有把鎖,想要報名的人必須先拿到鎖,然後才能更新名單。這就避免了多人同時更新消息,報名名單也就不會出錯了。

避免濫用鎖

難道遇到這種情況都用鎖?

如果能不用鎖,就不用鎖;

如果你不确定是不是應該用鎖,那也不要用鎖。

因為使用鎖雖然可以保護共享資源,但代價不小。

  1. 加鎖和解鎖都要CPU時間,這是性能損失。另外,使用鎖就有可能導緻線程等待鎖,等待鎖過程中線程是阻塞的狀态,過多的鎖等待會顯著降低程式的性能
  2. 如果鎖使用不當,很容易死鎖,導緻程式卡死。多線程本就難以調試,再加鎖,出現并發問題或者死鎖問題,程式更難調試。

是以,你在使用鎖以前,一定要非常清楚明确地知道,這個問題必須要用一把鎖來解決。切忌看到一個共享資料,也搞不清它在并發環境中會不會出現争用問題,就“為了保險,給它加個鎖吧。”千萬不能有這種不負責任的想法,否則你将會付出慘痛的代價!我曾經遇到過的嚴重線上事故,其中有幾次就是由于不當地使用鎖導緻的。

隻有并發下的共享資源不支援并發通路,或者并發通路共享資源會導緻系統錯誤的情況下,才需使用鎖。

鎖的用法

在通路共享資源之前,先擷取鎖。

如果擷取鎖成功,就可以通路共享資源了。

最後,需要釋放鎖,以便其他線程繼續通路共享資源。

Java使用鎖

private Lock lock = new ReentrantLock();

public void visitShareResWithLock() {
  lock.lock();
  try {
    // 在這裡安全的通路共享資源
  } finally {
    lock.unlock();
  }
}      

也可以使用synchronized關鍵字,它的效果和鎖是一樣的:

private Object lock = new Object();

public void visitShareResWithLock() {
  synchronized (lock) {
    // 在這裡安全的通路共享資源
  }
}      

使用鎖要注意:

使用完鎖一定要釋放。若在通路共享資源時抛異常,後面釋放鎖代碼就不會再執行,導緻死鎖。是以要考慮代碼可能走的所有分支,確定所有情況下的鎖都能釋放。

接下來我們說一下,使用鎖的時候,遇到的最常見的問題:死鎖。