一直以來併發編程對於剛入行的小白來講老是以爲高深莫測,因而乎,就誕生了想寫點東西記錄下,以提高理解和堆併發編程的認知。爲何須要用的併發?凡事總有好壞兩面,之間的trade-off是什麼,也就是說併發編程具備哪些缺點?以及在進行併發編程時應該瞭解和掌握的概念是什麼?這篇文章主要以這三個問題來談一談。java
一直以來,硬件的發展極其迅速,也有一個很著名的"摩爾定律",可能會奇怪明明討論的是併發編程爲何會扯到了硬件的發展,這其中的關係應該是多核CPU的發展爲併發編程提供的硬件基礎。摩爾定律並非一種天然法則或者是物理定律,它只是基於認爲觀測數據後,對將來的一種預測。按照所預測的速度,咱們的計算能力會按照指數級別的速度增加,不久之後會擁有超強的計算能力,正是在暢想將來的時候,2004年,Intel宣佈4GHz芯片的計劃推遲到2005年,而後在2004年秋季,Intel宣佈完全取消4GHz的計劃,也就是說摩爾定律的有效性超過了半個世紀戛然而止。可是,聰明的硬件工程師並無中止研發的腳步,他們爲了進一步提高計算速度,而不是再追求單獨的計算單元,而是將多個計算單元整合到了一塊兒,也就是造成了多核CPU。短短十幾年的時間,家用型CPU,好比Intel i7就能夠達到4核心甚至8核心。而專業服務器則一般能夠達到幾個獨立的CPU,每個CPU甚至擁有多達8個以上的內核。所以,摩爾定律彷佛在CPU核心擴展上繼續獲得體驗。所以,多核的CPU的背景下,催生了併發編程的趨勢,經過併發編程的形式能夠將多核CPU的計算能力發揮到極致,性能獲得提高。程序員
頂級計算機科學家Donald Ervin Knuth如此評價這種狀況:在我看來,這種現象(併發)或多或少是因爲硬件設計者機關用盡了致使的,他們將摩爾定律的責任推給了軟件開發者。面試
另外,在特殊的業務場景下先天的就適合於併發編程。好比在圖像處理領域,一張1024X768像素的圖片,包含達到78萬6千多個像素。即時將全部的像素遍歷一邊都須要很長的時間,面對如此複雜的計算量就須要充分利用多核的計算的能力。又好比當咱們在網上購物時,爲了提高響應速度,須要拆分,減庫存,生成訂單等等這些操做,就能夠進行拆分利用多線程的技術完成。面對複雜業務模型,並行程序會比串行程序更適應業務需求,而併發編程更能吻合這種業務拆分 。正是由於這些優勢,使得多線程技術可以獲得重視,也是一名CS學習者應該掌握的:算法
多線程技術有這麼多的好處,難道就沒有一點缺點麼,就在任何場景下就必定適用麼?很顯然不是。sql
時間片是CPU分配給各個線程的時間,由於時間很是短,因此CPU不斷經過切換線程,讓咱們以爲多個線程是同時執行的,時間片通常是幾十毫秒。而每次切換時,須要保存當前的狀態起來,以便可以進行恢復先前狀態,而這個切換時很是損耗性能,過於頻繁反而沒法發揮出多線程編程的優點。一般減小上下文切換能夠採用無鎖併發編程,CAS算法,使用最少的線程和使用協程。數據庫
無鎖併發編程:能夠參照concurrentHashMap鎖分段的思想,不一樣的線程處理不一樣段的數據,這樣在多線程競爭的條件下,能夠減小上下文切換的時間。編程
CAS算法:利用Atomic下使用CAS算法來更新數據,使用了樂觀鎖,能夠有效的減小一部分沒必要要的鎖競爭帶來的上下文切換安全
使用最少線程:避免建立不須要的線程,好比任務不多,可是建立了不少的線程,這樣會形成大量的線程都處於等待狀態bash
協程:在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換服務器
因爲上下文切換也是個相對比較耗時的操做,因此在"java併發編程的藝術"一書中有過一個實驗,併發累加未必會比串行累加速度要快。 可使用Lmbench3測量上下文切換的時長 vmstat測量上下文切換次數
多線程編程中最難以把握的就是臨界區線程安全問題,稍微不注意就會出現死鎖的狀況,一旦產生死鎖就會形成系統功能不可用。
public class DeadLockDemo {
private static String resource_a = "A";
private static String resource_b = "B";
public static void main(String[] args) {
deadLock();
}
public static void deadLock() {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resource_a) {
System.out.println("get resource a");
try {
Thread.sleep(3000);
synchronized (resource_b) {
System.out.println("get resource b");
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
);
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resource_b) {
System.out.println("get resource b");
synchronized (resource_a) {
System.out.println("get resource a");
}
}
}
}
);
threadA.start();
threadB.start();
}
}
複製代碼
在上面的這個demo中,開啓了兩個線程threadA, threadB,其中threadA佔用了resource_a, 並等待被threadB釋放的resource _b。threadB佔用了resource _b正在等待被threadA釋放的resource _a。所以threadA,threadB出現線程安全的問題,造成死鎖。一樣能夠經過jps,jstack證實這種推論:
"Thread-1":
waiting to lock monitor 0x000000000b695360 (object 0x00000007d5ff53a8, a java.lang.String),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000000000b697c10 (object 0x00000007d5ff53d8, a java.lang.String),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at learn.DeadLockDemo$2.run(DeadLockDemo.java:34)
- waiting to lock <0x00000007d5ff53a8(a java.lang.String)
- locked <0x00000007d5ff53d8(a java.lang.String)
at java.lang.Thread.run(Thread.java:722)
"Thread-0":
at learn.DeadLockDemo$1.run(DeadLockDemo.java:20)
- waiting to lock <0x00000007d5ff53d8(a java.lang.String)
- locked <0x00000007d5ff53a8(a java.lang.String)
at java.lang.Thread.run(Thread.java:722)
Found 1 deadlock.
複製代碼
如上所述,徹底能夠看出當前死鎖的狀況。
那麼,一般能夠用以下方式避免死鎖的狀況:
因此,如何正確的使用多線程編程技術有很大的學問,好比如何保證線程安全,如何正確理解因爲JMM內存模型在原子性,有序性,可見性帶來的問題,好比數據髒讀,DCL等這些問題(在後續篇幅會講述)。而在學習多線程編程技術的過程當中也會讓你收穫頗豐。
同步和異步一般用來形容一次方法調用。同步方法調用一開始,調用者必須等待被調用的方法結束後,調用者後面的代碼才能執行。而異步調用,指的是,調用者不用管被調用方法是否完成,都會繼續執行後面的代碼,當被調用的方法完成後會通知調用者。好比,在超時購物,若是一件物品沒了,你得等倉庫人員跟你調貨,直到倉庫人員跟你把貨物送過來,你才能繼續去收銀臺付款,這就相似同步調用。而異步調用了,就像網購,你在網上付款下單後,什麼事就不用管了,該幹嗎就幹嗎去了,當貨物到達後你收到通知去取就好。
併發和並行是十分容易混淆的概念。併發指的是多個任務交替進行,而並行則是指真正意義上的「同時進行」。實際上,若是系統內只有一個CPU,而使用多線程時,那麼真實系統環境下不能並行,只能經過切換時間片的方式交替進行,而成爲併發執行任務。真正的並行也只能出如今擁有多個CPU的系統中。
阻塞和非阻塞一般用來形容多線程間的相互影響,好比一個線程佔有了臨界區資源,那麼其餘線程須要這個資源就必須進行等待該資源的釋放,會致使等待的線程掛起,這種狀況就是阻塞,而非阻塞就剛好相反,它強調沒有一個線程能夠阻塞其餘線程,全部的線程都會嘗試地往前運行。
臨界區用來表示一種公共資源或者說是共享數據,能夠被多個線程使用。可是每一個線程使用時,一旦臨界區資源被一個線程佔有,那麼其餘線程必須等待。
分享免費學習資料
針對於Java程序員,我這邊準備免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)
爲何某些人會一直比你優秀,是由於他自己就很優秀還一直在持續努力變得更優秀,而你是否是還在知足於現狀心裏在竊喜!但願讀到這的您能點個小贊和關注下我,之後還會更新技術乾貨,謝謝您的支持!
資料領取方式:加入Java技術交流羣963944895
,點擊加入羣聊,私信管理員便可免費領取