Java 併發編程

来源:https://www.cnblogs.com/798911215-Darryl-Tang/archive/2018/06/01/9119987.html
-Advertisement-
Play Games

Java併發編程,你需要知道的 併發編程三要素 原子性 原子,即一個不可再被分割的顆粒。在Java中原子性指的是一個或多個操作要麼全部執行成功要麼全部執行失敗。 有序性 程式執行的順序按照代碼的先後順序執行。(處理器可能會對指令進行重排序) 可見性 當多個線程訪問同一個變數時,如果其中一個線程對其作 ...


 

              Java併發編程,你需要知道的

623504-3856f8a1e75a85c5

併發編程三要素

  • 原子性 原子,即一個不可再被分割的顆粒。在Java中原子性指的是一個或多個操作要麼全部執行成功要麼全部執行失敗。
  • 有序性 程式執行的順序按照代碼的先後順序執行。(處理器可能會對指令進行重排序)
  • 可見性 當多個線程訪問同一個變數時,如果其中一個線程對其作了修改,其他線程能立即獲取到最新的值。

2. 線程的五大狀態

  • 創建狀態 當用 new 操作符創建一個線程的時候
  • 就緒狀態 調用 start 方法,處於就緒狀態的線程並不一定馬上就會執行 run 方法,還需要等待CPU的調度
  • 運行狀態 CPU 開始調度線程,並開始執行 run 方法
  • 阻塞狀態 線程的執行過程中由於一些原因進入阻塞狀態 比如:調用 sleep 方法、嘗試去得到一個鎖等等
  • 死亡狀態 run 方法執行完 或者 執行過程中遇到了一個異常

3.悲觀鎖與樂觀鎖

  • 悲觀鎖:每次操作都會加鎖,會造成線程阻塞。
  • 樂觀鎖:每次操作不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止,不會造成線程阻塞。

4.線程之間的協作

4.1 wait/notify/notifyAll

這一組是 Object 類的方法 需要註意的是:這三個方法都必須在同步的範圍內調用

  • wait 阻塞當前線程,直到 notify 或者 notifyAll 來喚醒
  //wait有三種方式的調用
  wait()
  //必要要由 notify 或者 notifyAll 來喚醒
  wait(long timeout)
  //在指定時間內,如果沒有notify或notifAll方法的喚醒,也會自動喚醒。
  wait(long timeout,long nanos)
  //本質上還是調用一個參數的方法
  public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
               throw new IllegalArgumentException("timeout value is negative");
         }
        if (nanos < 0 || nanos > 999999) {
                throw new IllegalArgumentException(
               "nanosecond timeout value out of range");
         }
         if (nanos > 0) {
               timeout++;
         }
         wait(timeout);
  }


  • notify 只能喚醒一個處於 wait 的線程
  • notifyAll 喚醒全部處於 wait 的線程 ​

4.2 sleep/yield/join

這一組是 Thread 類的方法

  • sleep 讓當前線程暫停指定時間,只是讓出CPU的使用權,並不釋放鎖

  • yield 暫停當前線程的執行,也就是當前CPU的使用權,讓其他線程有機會執行,不能指定時間。會讓當前線程從運行狀態轉變為就緒狀態,此方法在生產環境中很少會使用到,官方在其註釋中也有相關的說明

        /**
        * A hint to the scheduler that the current thread is willing to yield
        * its current use of a processor. The scheduler is free to ignore this
        * hint.
        *
        * <p> Yield is a heuristic attempt to improve relative progression
        * between threads that would otherwise over-utilise a CPU. Its use
        * should be combined with detailed profiling and benchmarking to
        * ensure that it actually has the desired effect.
        *
        * <p> It is rarely appropriate to use this method. It may be useful
        * for debugging or testing purposes, where it may help to reproduce
        * bugs due to race conditions. It may also be useful when designing
        * concurrency control constructs such as the ones in the
        * {@link java.util.concurrent.locks} package.
        */


  • join 等待調用 join 方法的線程執行結束,才執行後面的代碼 其調用一定要在 start 方法之後(看源碼可知) 使用場景:當父線程需要等待子線程執行結束才執行後面內容或者需要某個子線程的執行結果會用到 join 方法

5.valitate 關鍵字

5.1 定義

java編程語言允許線程訪問共用變數,為了確保共用變數能被準確和一致的更新,線程應該確保通過排他鎖單獨獲得這個變數。Java語言提供了volatile,在某些情況下比鎖更加方便。如果一個欄位被聲明成volatile,java線程記憶體模型確保所有線程看到這個變數的值是一致的。

valitate是輕量級的synchronized,不會引起線程上下文的切換和調度,執行開銷更小。

5.2 原理

1. 使用volitate修飾的變數在彙編階段,會多出一條lock首碼指令 2. 它確保指令重排序時不會把其後面的指令排到記憶體屏障之前的位置,也不會把前面的指令排到記憶體屏障的後面;即在執行到記憶體屏障這句指令時,在它前面的操作已經全部完成 3. 它會強制將對緩存的修改操作立即寫入主存 4. 如果是寫操作,它會導致其他CPU里緩存了該記憶體地址的數據無效

5.3 作用

記憶體可見性 多線程操作的時候,一個線程修改了一個變數的值 ,其他線程能立即看到修改後的值 防止重排序 即程式的執行順序按照代碼的順序執行(處理器為了提高代碼的執行效率可能會對代碼進行重排序)

並不能保證操作的原子性(比如下麵這段代碼的執行結果一定不是100000)

    public class testValitate {
    public volatile int inc = 0;
    public void increase() {
        inc = inc + 1;
    }
    public static void main(String[] args) {
        final testValitate test = new testValitate();
        for (int i = 0; i < 100; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++)
                        test.increase();
                }
            }.start();
        }
        while (Thread.activeCount() > 2) {  //保證前面的線程都執行完
            Thread.yield();
        }
        System.out.println(test.inc);
     }
   }

6. synchronized 關鍵字

確保線程互斥的訪問同步代碼

6.1 定義

synchronized 是JVM實現的一種鎖,其中鎖的獲取和釋放分別是 monitorenter 和 monitorexit 指令,該鎖在實現上分為了偏向鎖、輕量級鎖和重量級鎖,其中偏向鎖在 java1.6 是預設開啟的,輕量級鎖在多線程競爭的情況下會膨脹成重量級鎖,有關鎖的數據都保存在對象頭中

6.2 原理

加了 synchronized 關鍵字的代碼段,生成的位元組碼文件會多出 monitorenter 和 monitorexit 兩條指令(利用javap -verbose 位元組碼文件可看到關,關於這兩條指令的文檔如下:

  • monitorenter Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor. • If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count. • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
  • monitorexit The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref. The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

加了 synchronized 關鍵字的方法,生成的位元組碼文件中會多一個 ACC_SYNCHRONIZED 標誌位,當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需通過位元組碼來完成。

6.3 關於使用

  • 修飾普通方法 同步對象是實例對象
  • 修飾靜態方法 同步對象是類本身
  • 修飾代碼塊 可以自己設置同步對象

6.4 缺點

會讓沒有得到鎖的資源進入Block狀態,爭奪到資源之後又轉為Running狀態,這個過程涉及到操作系統用戶模式和內核模式的切換,代價比較高。Java1.6為 synchronized 做了優化,增加了從偏向鎖到輕量級鎖再到重量級鎖的過度,但是在最終轉變為重量級鎖之後,性能仍然較低。

7. CAS

AtomicBoolean,AtomicInteger,AtomicLong以及 Lock 相關類等底層就是用 CAS實現的,在一定程度上性能比 synchronized 更高。

7.1 什麼是CAS

CAS全稱是Compare And Swap,即比較替換,是實現併發應用到的一種技術。操作包含三個操作數 —— 記憶體位置(V)、預期原值(A)和新值(B)。 如果記憶體位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值 。否則,處理器不做任何操作。

7.2 為什麼會有CAS

如果只是用 synchronized 來保證同步會存在以下問題 synchronized 是一種悲觀鎖,在使用上會造成一定的性能問題。在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。一個線程持有鎖會導致其它所有需要此鎖的線程掛起。

7.3 實現原理

Java不能直接的訪問操作系統底層,是通過native方法(JNI)來訪問。CAS底層通過Unsafe類實現原子性操作。

7.4 存在的問題

  • ABA問題 什麼是ABA問題?比如有一個 int 類型的值 N 是 1 此時有三個線程想要去改變它: 線程A :希望給 N 賦值為 2 線程B: 希望給 N 賦值為 2 線程C: 希望給 N 賦值為 1 此時線程A和線程B同時獲取到N的值1,線程A率先得到系統資源,將 N 賦值為 2,線程 B 由於某種原因被阻塞住,線程C線上程A執行完後得到 N 的當前值2 此時的線程狀態 線程A成功給 N 賦值為2 線程B獲取到 N 的當前值 1 希望給他賦值為 2,處於阻塞狀態 線程C獲取當好 N 的當前值 2 希望給他賦值為1 ​ 然後線程C成功給N賦值為1 最後線程B得到了系統資源,又重新恢復了運行狀態,在阻塞之前線程B獲取到的N的值是1,執行compare操作發現當前N的值與獲取到的值相同(均為1),成功將N賦值為了2。 ​ 在這個過程中線程B獲取到N的值是一個舊值,雖然和當前N的值相等,但是實際上N的值已經經歷了一次 1到2到1的改變 上面這個例子就是典型的ABA問題 怎樣去解決ABA問題 給變數加一個版本號即可,在比較的時候不僅要比較當前變數的值 還需要比較當前變數的版本號。Java中AtomicStampedReference 就解決了這個問題
  • 迴圈時間長開銷大 在併發量比較高的情況下,如果許多線程反覆嘗試更新某一個變數,卻又一直更新不成功,迴圈往複,會給CPU帶來很大的壓力。

CAS只能保證一個共用變數的原子操作

8. AbstractQueuedSynchronizer(AQS)

AQS抽象的隊列式同步器,是一種基於狀態(state)的鏈表管理方式。state 是用CAS去修改的。它是 java.util.concurrent 包中最重要的基石,要學習想學習 java.util.concurrent 包里的內容這個類是關鍵。 ReentrantLock、CountDownLatcher、Semaphore 實現的原理就是基於AQS。想知道他怎麼實現以及實現原理 可以參看這篇文章https://www.cnblogs.com/waterystone/p/4920797.html

9. Future

在併發編程我們一般使用Runable去執行非同步任務,然而這樣做我們是不能拿到非同步任務的返回值的,但是使用Future 就可以。使用Future很簡單,只需把Runable換成FutureTask即可。使用上比較簡單,這裡不多做介紹。

10. 線程池

如果我們使用線程的時候就去創建一個線程,雖然簡單,但是存在很大的問題。如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷毀線程需要時間。線程池通過復用可以大大減少線程頻繁創建與銷毀帶來的性能上的損耗。

Java中線程池的實現類 ThreadPoolExecutor,其構造函數的每一個參數的含義在註釋上已經寫得很清楚了,這裡幾個關鍵參數可以再簡單說一下

  • corePoolSize :核心線程數即一直保留線上程池中的線程數量,即使處於閑置狀態也不會被銷毀。要設置 allowCoreThreadTimeOut 為 true,才會被銷毀。
  • maximumPoolSize:線程池中允許存在的最大線程數
  • keepAliveTime :非核心線程允許的最大閑置時間,超過這個時間就會本地銷毀。
  • workQueue:用來存放任務的隊列。
    • SynchronousQueue:這個隊列會讓新添加的任務立即得到執行,如果線程池中所有的線程都在執行,那麼就會去創建一個新的線程去執行這個任務。當使用這個隊列的時候,maximumPoolSizes一般都會設置一個最大值 Integer.MAX_VALUE
    • LinkedBlockingQueue:這個隊列是一個無界隊列。怎麼理解呢,就是有多少任務來我們就會執行多少任務,如果線程池中的線程小於corePoolSize ,我們就會創建一個新的線程去執行這個任務,如果線程池中的線程數等於corePoolSize,就會將任務放入隊列中等待,由於隊列大小沒有限制所以也被稱為無界隊列。當使用這個隊列的時候 maximumPoolSizes 不生效(線程池中線程的數量不會超過corePoolSize),所以一般都會設置為0。
    • ArrayBlockingQueue:這個隊列是一個有界隊列。可以設置隊列的最大容量。當線程池中線程數大於或者等於 maximumPoolSizes 的時候,就會把任務放到這個隊列中,噹噹前隊列中的任務大於隊列的最大容量就會丟棄掉該任務交由 RejectedExecutionHandler 處理。

最後,本文主要對Java併發編程開發需要的知識點作了簡單的講解,這裡每一個知識點都可以用一篇文章去講解,由於篇幅原因不能對每一個知識點都詳細介紹,我相信通過本文你會對Java的併發編程會有更近一步的瞭解。如果您發現還有缺漏或者有錯誤的地方,可以在評論區補充,謝謝。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 第一次接觸 "qs" 這個庫,是在使用axios時,用於給post方法編碼,在使用過程中,接觸到了一些不同的用法,寫在這裡分享一下。 qs.parse 方法可以把一段格式化的字元串轉換為對象格式,比如 qs.stringify 基本用法 則和 相反,是把一個參數對象格式化為一個字元串。 排序 甚至可 ...
  • 出錯提示: angular2 Property 'map' does not exist on type 'Observable<Response>' 類型“Observable<Response>”上不存在屬性“map” 解決: 引入 rxjs/add/operator/map ...
  • 長期更新IT編程視頻教程,資料收集整理不易,需要一點費用 有意者加QQ:2773400,非誠勿擾! ...
  • 01.第一階段、Svn版本管理與代碼上線架構方案 02.第二階段、實戰Java高併發程式設計模式視頻 03.第三階段、深入JVM內核—原理、診斷與優化 04.第四階段、基於Netty的RPC架構實戰演練 05.第五階段、Git分散式版本控制系統權威指南 06.第六階段、Redis從入門到精通、集群與 ...
  • 本書是Eric Evans對他自己寫的《領域驅動設計-軟體核心複雜性應對之道》的一本字典式的參考書,可用於快速查找《領域驅動設計》中的諸多概念及其簡明解釋。 其它本系列其它文章地址: [譯文]Domain Driven Design Reference(一)—— 前言 [譯文]Domain Driv ...
  • 1. 介紹 Commonservice-system是一個大型分散式、微服務、面向企業的JavaEE體系快速研發平臺,基於模塊化、服務化、原子化、熱插拔的設計思想,使用成熟領先的無商業限制的主流開源技術構建。採用服務化的組件開發模式,可實現複雜的業務功能。提供驅動式開發模式,整合內置的代碼生成器,將 ...
  • Spring Cloud是一系列框架的有序集合。利用Spring Boot的開發模式簡化了分散式系統基礎設施的開發,都可以用Spring Boot的開發風格做到一鍵啟動和部署。Spring Cloud將目前比較成熟、經得起實際考驗的服務框架組合起來,通過Spring Boot風格進行再封裝,屏蔽掉了 ...
  • 長期更新IT編程視頻教程,資料收集整理不易,需要一點費用 有意者加QQ:2773400,非誠勿擾! 長期更新IT編程視頻教程,資料收集整理不易,需要一點費用 有意者加QQ:2773400,非誠勿擾! ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...