java併發簡述

1.爲何要用到併發?

一個程序內部能擁有多個線程並行執行。一個線程的執行能夠被認爲是一個CPU在執行該程序。當一個程序運行在多線程下,就好像有多個CPU在同時執行該程序。這原本是件好事,多線程執行,大大提升了咱們的程序運行效率。
但使人頭疼的多線程安全問題,也隨之而來。若是一個線程在讀一個內存時,另外一個線程正向該內存進行寫操做,那進行讀操做的那個線程將得到什麼結果呢?是寫操做以前舊的值?仍是寫操做成功以後的新值?或是一半新一半舊的值?

或者,若是是兩個線程同時寫同一個內存,在操做完成後將會是什麼結果呢?** 是第一個線程寫入的值?仍是第二個線程寫入的值?仍是兩個線程寫入的一個混合值?
所以如沒有合適的預防措施,任何結果都是可能的。並且這種行爲的發生甚至不能預測,因此結果也是不肯定性的。

因此綜合而言,學習併發編程有最重要的2點。
 一、理解併發編程原理,便於咱們更好的提升CPU的使用率,加快任務執行速度,下降系統響應時間;
 二、運用好併發編程能幫咱們很好地解決多線程安全問題;
複製代碼

併發編程在必定程度上離不開多核CPU的發展。隨着單核CPU的研發已經不能遵循「摩爾定律」(摩爾定律是硬件發展的觀測定律,另外還有基於「摩爾定律」的「反摩爾定律」,不過「反摩爾定律」是軟件領域的定律,有興趣的能夠自行了解),硬件工程師們爲了進一步提高計算速度,而不是再追求單獨的計算單元,而是將多個計算單元整合到了一塊兒,也就是造成了多核CPU。短短十幾年的時間,家用型CPU,好比Intel i7就能夠達到4核心甚至8核心。而專業服務器則一般能夠達到幾個獨立的CPU,每個CPU甚至擁有多達8個以上的內核。算法

所以,「摩爾定律」彷佛在CPU核心擴展上繼續獲得體驗。而在多核的CPU的背景下,催生了併發編程的趨勢,通併發編程的形式能夠將多核CPU的計算能力發揮到極致,性能獲得提高。數據庫

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

另外在開發購物平臺時,爲了提高響應速度,須要拆分,減庫存,生成訂單等等這些操做,就能夠進行拆分利用多線程的技術完成。面對複雜業務模型,並行程序會比串行程序更適應業務需求,而併發編程吻合更能這種業務拆分正是由於這些優勢,使得多線程技術可以獲得重視,也是一名CS學習者應該掌握的:安全

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

  • 方便進行業務拆分,提高應用性能服務器

2.併發編程有哪些缺點?

2.1 頻繁的上下文切換 時間片是CPU分配給各個線程的時間,由於時間很是短,因此CPU不斷經過切換線程,讓咱們以爲多個線程是同時執行的,時間片通常是幾十毫秒。多線程

每次切換時,須要把當前的狀態保存起來,以便可以進行恢復先前狀態,而這個切換行爲很是損耗性能,過於頻繁切換反而沒法發揮出多線程編程的優點。一般減小上下文切換能夠採用無鎖併發編程、 CAS算法、使用最少的線程和使用協程。併發

無鎖併發編程:能夠參照的ConcurrentHashMap鎖分段的思想,不一樣的線程處理不一樣段的數據,這樣在多線程競爭的條件下,能夠減小上下文切換的時間。app

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…

相關文章
相關標籤/搜索