不可變對象java
不可變對象(Immutable Objects)是指對象一旦被建立它的狀態(對象的數據,也即對象屬性值)就不能改變,任何對它的改變都應該產生一個新的對象。spring
不可變對象須要知足的條件:數據庫
除了使用final自行封裝不可變對象以外,還能夠經過如下兩種方式定義不可變對象api
線程封閉數組
當訪問共享的可變數據時,一般須要同步。一種避免同步的方式就是不共享數據。若是僅在單線程內訪問數據,就不須要同步,這種技術稱爲線程封閉。安全
常見線程封閉手段:數據結構
spring中必定要在攔截器afterCompletion中,執行threadlocal的remove函數,線程池中使用同理。多線程
同步容器架構
stringbuilder:線程不安全(能夠在函數中定義,利用堆棧封閉避免了線程不安全,同時節省了加鎖的消耗,性能更好)併發
stringbuffer:線程安全(每一個函數都是用synchronized修飾),能夠作全局變量。
SimpleDateFormat:JDK中的工具類,線程不安全。使用方法能夠參考stringbuilder。
JodaTime:線程安全,功能更豐富。
ArrayList/HashSet/HashMap等Collections:都是線程不安全的
Vector/Stack/HashTable:都是線程安全的
先檢查再執行:if(condition(a)){handle(a)},這種形式若是沒有加鎖的話,就不是原子性,也是線程不安全的
併發容器
線程安全的容器除了上文提到的同步容器一些外,在Java的J.U.C(java.utils.concurrent的縮寫)下,一樣提供了線程安全的併發容器。
注意:併發容器的批量操做都不是線程安全的,例如調用removeAll,containsAll等,須要自行加鎖。
CopyOnWriteArrayList、CopyOnWriteArraySet,這種利用cow特性的數據結構,須要copy消耗內存,可能引起gc。
想免費學習(Java工程化、分佈式架構、高併發、高性能、深刻淺出、微服務架構、Spring、MyBatis、Netty、源碼分析)等技術的朋友,能夠加羣:834962734,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們,歡迎進羣一塊兒深刻交流學習,無論你是轉行,仍是工做中想提高本身能力均可以!
線程死鎖是指因爲兩個或者多個線程互相持有對方所須要的資源,致使這些線程處於等待狀態,沒法前往執行。
死鎖的必要條件
死鎖示例代碼:
@Slf4j public class DeadLock implements Runnable { public int flag = 1; //靜態對象是類的全部對象共享的 private static Object o1 = new Object(), o2 = new Object(); @Override public void run() { log.info("flag:{}", flag); if (flag == 1) { synchronized (o1) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o2) { log.info("1"); } } } if (flag == 0) { synchronized (o2) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o1) { log.info("0"); } } } } public static void main(String[] args) { DeadLock td1 = new DeadLock(); DeadLock td2 = new DeadLock(); td1.flag = 1; td2.flag = 0; //td1,td2都處於可執行狀態,但JVM線程調度先執行哪一個線程是不肯定的。 //td2的run()可能在td1的run()以前運行 new Thread(td1).start(); new Thread(td2).start(); } }
避免死鎖的方法
死鎖排查方法
雖然形成死鎖的緣由是由於咱們設計得不夠好,可是可能寫代碼的時候不知道哪裏發生了死鎖。
JDK提供了兩種方式來給咱們檢測:
檢測出死鎖時的解決方案
一個可行的作法是釋放全部鎖,回退,而且等待一段隨機的時間後重試。這個和簡單的加鎖超時相似,不同的是隻有死鎖已經發生了纔回退,而不會是由於加鎖的請求超時了。雖然有回退和等待,可是若是有大量的線程競爭同一批鎖,它們仍是會重複地死鎖(編者注:緣由同超時相似,不能從根本上減輕競爭)。
一個更好的方案是給這些線程設置優先級,讓一個(或幾個)線程回退,剩下的線程就像沒發生死鎖同樣繼續保持着它們須要的鎖。若是賦予這些線程的優先級是固定不變的,同一批線程老是會擁有更高的優先級。爲避免這個問題,能夠在死鎖發生的時候設置隨機的優先級。
1. 使用本地變量
儘可能使用本地變量,而不是建立一個類或實例的變量。
class concurrentTask { private static List temp = new ArrayList<>(); public void execute(Message message) { // 使用本地變量保證線程安全 // List temp = new ArrayList<>(); temp.add(message.getId()); temp.add(message.getCode()); // ...省略各類業務邏輯 temp.clear(); } }
2. 使用不可變類
不可變類好比String 、Integer等一旦建立,再也不改變,不可變類能夠下降代碼中須要的同步數量。
3. 最小化鎖的做用域範圍
"阿姆達爾定律",又稱"安達爾定理": S=1/(1-a+a/n)
a:並行計算部分所佔比例
n:並行處理結點個數
S:加速比
當1-a等於0時,沒有串行只有並行,最大加速比 S=n
當a=0時,只有串行沒有並行,最小加速比 S = 1
當n→∞時,極限加速比 s→ 1/(1-a)
例如,若串行代碼佔整個代碼的25%,則並行處理的整體性能不可能超過4。
4. 使用線程池,而不是直接使用new Thread執行
避免new Thread建立線程。經過線程池的管理,可提升線程的複用性(避免新建線程的昂貴的資源消耗),簡化線程生命週期的管理。JDK提供了各類ThreadPool線程池和Executor。
5. 寧肯使用同步工具類也不要使用線程的wait和notify
同步工具類包括:countdownlaunch/Semaphore/Semaphore。應當優先使用這些同步工具,而不是去思考如何使用線程的wait和notify。此外,使用BlockingQueue實現生產消費的設計比使用wait和notify要好。
6. 使用blockingqueue實現生產消費模式
阻塞隊列是生產者-消費者模式的最好的實現方式,不只包括單個生產者單個消費者,還支持多個生產者多個消費者狀況。
7. 使用併發集合而不是加了鎖的同步集合
JDK提供了ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、BlockingQueue中的Deque和BlockingDeque五大併發集合,他們有着較好性能;儘可能使用該併發集合,而避免使用synchronizedXXX的鎖同步集合。
8. 使用semaphore建立有界的訪問
爲了創建穩定可靠的系統,對於數據庫、文件系統和socket等資源必需要作有界的訪問,Semaphone能夠限制這些資源開銷的選擇,Semaphone能夠以最低的代價阻塞線程等待,能夠經過Semaphone來控制同時訪問指定資源的線程數。
9. 寧肯使用同步代碼塊,也不使用同步的方法
主要針對synchronized關鍵字。使用synchronized關鍵字同步代碼塊只會鎖定一個對象,而不會將整個方法鎖定(當類不是單例的時候)。若是更改共同的變量或類的字段,首先應該選擇的是原子型變量,而後使用volatile。若是須要互斥鎖,能夠考慮使用ReentrantLock。
10. 避免使用靜態變量
靜態變量在多線程併發環境中會形成較多的問題。當使用靜態變量時,優先將其指定爲final變量,若用其來保存集合Collection變量,則考慮使用只讀集合。詳見上文的不可變對象,同步容器和併發容器。
咱們老是想得太多,作的太少!