Java多線程(併發)淺談

        緣由:很明顯多線程就是爲了提高辦事的效率,因爲單線程的處理效率相對來說越來越達不到要求了,隨着硬件這塊的提升(CPU多核的出現),這樣也提高了CPU的使用率,不至於導致資源的浪費,多個任務可以並行執行,如果一次執行多個任務,如果其中一個出現了阻塞,可能會導致與當前任務無關的的任務也會阻塞,這裏就引入了線程(根據不同的任務去創建不同的線程)這樣就可以避免不相關的的任務之間相互不受影響。線程可以理解輕量級的進程,一個進程可能有很多個線程組成的

如上圖,單核的CPU任何時候都只有一個任務在執行,CPU時間片的不停切換,讓你以爲是並行執行

線程的應用

        方式:1、繼承(extend) 2、實現( Runnable)3、實現  Callable  接口通過 k FutureTask  包裝器來創建  Thread  線程

        場景:大部分還是通過批量操作,或者執行的數據量較大的時候,分線程執行,提升效率

線程相關的基礎概念以及狀態的轉換

     6個狀態:new(新建), runnable(執行),blocked(阻塞),wating(等待),time_wating(超時等待) ,terminated(終止)

      new(新建):創建一個Thread對象,但還未調用start()啓動線程,處於初始態。

      runnable(執行):運行狀態,Java中,運行態包括就緒態和運行態,就緒狀態就是獲得 了需要執行的資源(即滿足執行的條件了,就等CPU的執行命令了,存入在就緒隊列中),運行態就是獲得了CPU的執行權,正在執行。

     blocked(阻塞):即正在執行的線程,在調用某一資源是沒獲取到,那麼就會進入阻塞狀態。在Java中,阻塞態(同步阻塞)專指請求鎖失敗時進入的狀態。由一個阻塞隊列存放所有阻塞態的線程。處於阻塞態的線程會不斷請求資源,一旦請求成功,就會進入就緒隊列,等待執行。PS:鎖、IO、Socket等都資源。阻塞分爲:1、等待阻塞:運行了wait方法,jvm會將其放入等待隊列。2、同步阻塞 3、其他阻塞,執行sleep,join,I/O請求,jvm會講將當前線程設爲阻塞,等結束了sleep,join,I/O等再恢復之前狀態。

   wating(等待):當前線程中調用wait、join、park函數時,當前線程就會進入等待態。也有一個等待隊列存放所有等待態的線程。線程處於等待態表示它需要等待其他線程的指示才能繼續運行。進入等待態的線程會釋放CPU執行權,並釋放資源(如:鎖)

   time_wating(超時等待):超時等待,超時後自動返回

   terminated(終止):終止,當前線程執行完畢

引發的問題

     很顯然如果多線程並行(共享變量)的情況下,是否會有數據的安全性問題!當然這種問題其實大家都早就瞭解過的,但是爲什麼會導致這個問題,原因就是:多個線程都幾乎同時去請求這個共享變量,並且改變了,但是線程之間的數據時獨立的,並不是共享的,所以導致每個線程第一次獲取變量時時一樣的,但是各自修改之後就導致了變量的不一致了。那麼如何處理這個問題呢? 很容易想到的就是加鎖(悲觀鎖,樂觀鎖)

 在java中提供加鎖的方式就是Synchronizd關鍵字的方式

這裏就先簡單的介紹下這個關鍵字的用法  1. 修飾實例方法,作用於當前實例加鎖,進入同步代碼前要獲得當前實例的鎖    2. 靜態方法,作用於當前類對象加鎖,進入同步代碼前要獲得當前類對象的鎖    3. 修飾代碼塊,指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。不同的修飾類型,代表鎖的控制粒度

      關於這個鎖的粒度這邊就在說下:當你傳入的是這個對象的所屬大小(1、this  2、Object  比較靈活有傳入的參數決定 3、class 類層面)等 這邊就不再贅述了(注意:synchronized括號後的對象不同,那麼鎖也就不同了)

    既然說到了鎖,那麼我首先想到的就是鎖是如何實現的,應該包含算法吧? 是否需要存儲下來,又是怎麼存儲的?

  這個Lock對象有個markword的概念, 記錄了對象和鎖有關的信息,當某個對象被synchronized 關鍵字當成同步鎖時,那麼圍繞這個鎖的一系列操作都和 Mark word 有關係

   如果一個線程加鎖了,那麼其效率必然會受到影響的,那麼不加鎖又不行,加鎖又會影響效率。該怎麼辦呢? 那就是該加鎖就加鎖,不該加鎖儘量不加。這就有了後面的鎖的升級  (偏向鎖 - > 輕量級鎖  - > 重量級鎖)

   1、 偏向鎖 (適用於一個線程,沒有競爭的情況下

        原理:當一個線程訪問同步代碼塊的時候,對象頭會裏面當前線程的ID ,這樣下次訪問的訪問的時候,cas比較當前線程ID是否爲同一個就是可以了 ,就不需要反覆的加鎖和釋放鎖了。如cas失敗說明存在所競爭,就需要鎖升級了(輕量級鎖)

 2、輕量級鎖(2個線程

        原理:輕量級鎖死由於通過偏向鎖進行升級得到了,說明存在競爭,就是在cas的時候沒有成功,因此得到了輕量級鎖。輕量級鎖用到了自旋(for循環,執行一段獲取鎖的邏輯)。當然自旋次數也是一定的次數的,並非無限循環的。JDk1.6之後已有優化(根據前一次的自旋時間,以及擁有者的狀態來決定的)

3、重量級鎖(多個線程

        原理: 當輕量級鎖膨脹到重量級鎖之後,意味着線程只能被掛起阻塞來等待被喚醒了(每個對象會有monitor) ObjectMonitor重量級鎖的核心,Monitor鎖監視器。只有當monitorenter(獲得監視器對象) monitorexit(釋放鎖),所以只有當釋放了,才能獲得鎖,否則就會阻塞,阻塞之後會將阻塞的線程加入都隊列中,,只有當monitorexit,就會去隊列中喚醒其他處於阻塞中的線程,去重新競爭鎖。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章