java併發簡述

率性修道我心光明發表於2019-03-23

1.為什麼要用到併發?

一個程式內部能擁有多個執行緒並行執行。一個執行緒的執行可以被認為是一個CPU在執行該程式。當一個程式執行在多執行緒下,就好像有多個CPU在同時執行該程式。這本來是件好事,多執行緒執行,大大提高了我們的程式執行效率。
但令人頭疼的多執行緒安全問題,也隨之而來。如果一個執行緒在讀一個記憶體時,另一個執行緒正向該記憶體進行寫操作,那進行讀操作的那個執行緒將獲得什麼結果呢?是寫操作之前舊的值?還是寫操作成功之後的新值?或是一半新一半舊的值?

或者,如果是兩個執行緒同時寫同一個記憶體,在操作完成後將會是什麼結果呢?** 是第一個執行緒寫入的值?還是第二個執行緒寫入的值?還是兩個執行緒寫入的一個混合值?
因此如沒有合適的預防措施,任何結果都是可能的。而且這種行為的發生甚至不能預測,所以結果也是不確定性的。

所以綜合而言,學習併發程式設計有最重要的2點。
 1、理解併發程式設計原理,便於我們更好的提高CPU的使用率,加快任務執行速度,降低系統響應時間;
 2、運用好併發程式設計能幫我們很好地解決多執行緒安全問題;
複製程式碼

併發程式設計在一定程度上離不開多核CPU的發展。隨著單核CPU的研發已經不能遵循“摩爾定律”(摩爾定律是硬體發展的觀測定律,另外還有基於“摩爾定律”的“反摩爾定律”,不過“反摩爾定律”是軟體領域的定律,有興趣的可以自行了解),硬體工程師們為了進一步提升計算速度,而不是再追求單獨的計算單元,而是將多個計算單元整合到了一起,也就是形成了多核CPU。短短十幾年的時間,家用型CPU,比如Intel i7就可以達到4核心甚至8核心。而專業伺服器則通常可以達到幾個獨立的CPU,每一個CPU甚至擁有多達8個以上的核心。

因此,“摩爾定律”似乎在CPU核心擴充套件上繼續得到體驗。而在多核的CPU的背景下,催生了併發程式設計的趨勢,通併發程式設計的形式可以將多核CPU的計算能力發揮到極致,效能得到提升。

在特殊的業務場景下先天的就適合於併發程式設計。比如在影象處理領域,一張1024X768畫素的圖片,包含達到78萬6千多個畫素。即時將所有的畫素遍歷一邊都需要很長的時間,面對如此複雜的計算量就需要充分利用多核的計算的能力。

另外在開發購物平臺時,為了提升響應速度,需要拆分,減庫存,生成訂單等等這些操作,就可以進行拆分利用多執行緒的技術完成。面對複雜業務模型,並行程式會比序列程式更適應業務需求,而併發程式設計吻合更能這種業務拆分正是因為這些優點,使得多執行緒技術能夠得到重視,也是一名CS學習者應該掌握的:

  • 充分利用多核CPU的計算能力;

  • 方便進行業務拆分,提升應用效能

2.併發程式設計有哪些缺點?

2.1 頻繁的上下文切換 時間片是CPU分配給各個執行緒的時間,因為時間非常短,所以CPU不斷通過切換執行緒,讓我們覺得多個執行緒是同時執行的,時間片一般是幾十毫秒。

每次切換時,需要把當前的狀態儲存起來,以便能夠進行恢復先前狀態,而這個切換行為非常損耗效能,過於頻繁切換反而無法發揮出多執行緒程式設計的優勢。通常減少上下文切換可以採用無鎖併發程式設計、 CAS演算法、使用最少的執行緒和使用協程。

無鎖併發程式設計:可以參照的ConcurrentHashMap鎖分段的思想,不同的執行緒處理不同段的資料,這樣在多執行緒競爭的條件下,可以減少上下文切換的時間。

CAS演算法,利用原子下使用CAS演算法來更新資料,使用了樂觀鎖,可以有效的減少一部分不必要的鎖競爭帶來的上下文切換

使用最少執行緒:避免建立不需要的執行緒,比如任務很少,但是建立了很多的執行緒,這樣會造成大量的執行緒都處於等待狀態

協程:在單執行緒裡實現多工的排程,並在單執行緒裡維持多個任務間的切換

由於上下文切換是個相對比較耗時的操作,所以在 “Java的併發程式設計的藝術” 一書中有過一個實驗,併發累加未必會比序列累加速度快。

2.2 執行緒的安全性問題

多執行緒程式設計中最難以把握的就是臨界區執行緒安全問題,稍微不注意就會出現死鎖的情況,一旦產生死鎖就會造成系統功能不可用。

public class DeadLockDemo {

    private static String demo_a = "A";
    private static String demo_b = "B";

    public static void main(String[] args) {
        deadLock();
    }

    public static void deadLock() {

        Thread thread_a = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (demo_a){
                    System.out.println("get demo a");

                    try {
                        Thread.sleep(3000);
                        synchronized (demo_b){
                            System.out.println("get demo b");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }


            }
        });


        Thread thread_b = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (demo_b){
                    System.out.println("get demo b");
                    synchronized (demo_a){
                        System.out.println("get demo a ");
                    }
                }
            }
        });


        thread_a.start();
        thread_b.start();
    }
}

複製程式碼

在上面的這個demo中,開啟了兩個執行緒thread_a,thread_b,其中thread_a佔用了demo_a,並等待被thread_b釋放的資源demo_b,thread_b佔用了資源demo_b正在等待被thread_a釋放的資源demo_a。

因此thread_a,thread_b出現執行緒安全的問題,形成死鎖。

通常可以用如下方式避免死鎖的情況:

避免一個執行緒同時獲得多個鎖;

避免一個執行緒在鎖內部佔有多個資源,儘量保證每個鎖只佔用一個資源;

嘗試使用定時鎖,使用lock.tryLock(timeOut),當超時等待時當前執行緒不會阻塞;

對於資料庫鎖,加鎖和解鎖必須在一個資料庫連線裡,否則會出現解鎖失敗的情況

所以,如何正確的使用多執行緒程式設計技術有很大的學問,比如如何保證執行緒安全,如何正確理解由於JMM記憶體模型在原子性,有序性,可見性帶來的問題,比如資料髒讀,DCL等這些問題(在後續篇幅會講述)。而在學習多執行緒程式設計技術的過程中也會讓你收穫頗豐。 3. 需要了解的一些概念 3.1 同步VS非同步

同步和非同步通常用來形容一次方法呼叫。同步方法呼叫開始後,呼叫者必須等待被呼叫的方法結束後,呼叫者後面的程式碼才能執行。而非同步呼叫,指的是,呼叫者不用管被呼叫方法是否完成,都會繼續執行後面的程式碼,當被呼叫的方法完成後會通知呼叫者。

3.2 併發與並行

併發和並行是十分容易混淆的概念。併發指的是多個任務交替進行,而並行則是指真正意義上的“同時進行”。實際上,如果系統內只有一個CPU,使用多執行緒時,在真實系統環境下不能並行,只能通過切換時間片的方式交替進行,從而併發執行任務。真正的並行只能出現在擁有多個CPU的系統中。

3.3 阻塞和非阻塞

阻塞和非阻塞通常用來形容多執行緒間的相互影響,比如一個執行緒佔有了臨界區資源,那麼其他執行緒需要這個資源就必須進行等待該資源的釋放,會導致等待的執行緒掛起,這種情況就是阻塞,而非阻塞就恰好相反,它強調沒有一個執行緒可以阻塞其他執行緒,所有的執行緒都會嘗試地往前執行。

3.4 臨界區

臨界區用來表示公共資源或者說是共享資料,可以被多個執行緒使用。但是每個執行緒使用時,一旦臨界區資源被一個執行緒佔有,那麼其他執行緒必須等待。

Java併發系列一:什麼是併發? blog.csdn.net/TzBugs/arti…

Java併發系列二:執行緒的建立、狀態轉換及基本操作 blog.csdn.net/tzbugs/arti…

Java併發系列三:Java記憶體模型以及happens-before規則 blog.csdn.net/tzbugs/arti…

相關文章