java高併發系列-第1天:必須知道的幾個概念

来源:https://www.cnblogs.com/itsoku123/archive/2019/07/14/11185636.html
-Advertisement-
Play Games

java高併發系列 第1天:必須知道的幾個概念 同步(Synchronous)和非同步(Asynchronous) 同步和非同步通常來形容一次方法調用, 同步方法調用一旦開始,調用者必須等到方法調用返回後,才能繼續後續的行為 。 非同步方法調用更像一個消息傳遞,一旦開始,方法調用就會立即返回,調用者就可以 ...


java高併發系列-第1天:必須知道的幾個概念

同步(Synchronous)和非同步(Asynchronous)

同步和非同步通常來形容一次方法調用,同步方法調用一旦開始,調用者必須等到方法調用返回後,才能繼續後續的行為非同步方法調用更像一個消息傳遞,一旦開始,方法調用就會立即返回,調用者就可以繼續後續的操作。而非同步方法通常會在另外一個線程中“真實”地執行。整個過程,不會阻礙調用者的工作。

如圖:

上圖中顯示了同步方法調用和非同步方法調用的區別。對於調用者來說,非同步調用似乎是一瞬間就完成的。如果非同步調用需要返回結果,那麼當這個非同步調用真實完成時,則會通知調用者。

打個比方,比如購物,如果你去商場買空調,當你到了商場看重了一款空調,你就向售貨員下單。售貨員去倉庫幫你調配物品。這天你熱的是在不行了,就催著商家趕緊給你送貨,於是你就在商店裡面候著他們,直到商家把你和空調一起送回家,一次愉快的購物就結束了。這就是同步調用。

不過,如果我們趕時髦,就坐在家裡打開電腦,在電腦上訂購了一臺空調。當你完成網上支付的時候,對你來說購物過程已經結束了。雖然空調還沒有送到家,但是你的任務已經完成了。商家接到你的訂單後,就會加緊安平送貨,當然這一切已經跟你無關了。你已經支付完成,想乾什麼就能去乾什麼,出去溜幾圈都不成問題,等送貨上門的時候,接到商家的電話,回家一趟簽收就完事了。這就是非同步調用。

併發(Concurrency)和並行(Parallelism)

併發和並行是兩個非常容易被混淆的概念。他們都可以表示兩個或者多個任務一起執行,但是側重點有所不同。併發偏重於多個任務交替執行,而多個任務之間有可能還是串列的,而並行是真正意義上的“同時執行”,下圖很好地詮釋了這點。

大家排隊在一個咖啡機上接咖啡,交替執行,是併發;兩台咖啡機上面接咖啡,是並行。

從嚴格意義上來說,並行的多任務是真的同時執行,而對於併發來說,這個過程只是交替的,一會執行任務A,一會執行任務B,系統會不停地在兩者之間切換。但對於外部觀察者來說,即使多個任務之間是串列併發的,也會造成多任務間並行執行的錯覺。

併發說的是在一個時間段內,多件事情在這個時間段內交替執行

並行說的是多件事情在同一個時刻同事發生。

實際上,如果系統內只有一個CPU,而使用多進程或者多線程任務,那麼真實環境中這些任務不可能是真實並行的,畢竟一個CPU一次只能執行一條指令,在這種情況下多進程或者多線程就是併發的,而不是並行的(操作系統會不停地切換多任務)。真實的並行也只可能出現在擁有多個CPU的系統中(比如多核CPU)。

臨界區

臨界區用來表示一種公共資源或者說共用數據,可以被多個線程使用,但是每一次只能有一個線程使用它,一旦臨界區資源被占用,其他線程要想使用這個資源就必須等待。

比如,一個辦公室里有一臺印表機,印表機一次只能執行一個任務。如果小王和小明同時需要列印文件,很明顯,如果小王先發了列印任務,印表機就開始列印小王的文件,小明的任務就只能等待小王列印結束後才能列印,這裡的印表機就是一個臨界區的例子。

在並行程式中,臨界區資源是保護的對象,如果意外出現印表機同時執行兩個任務的情況,那麼最有可能的結果就是列印出來的文件是損壞的文件,它既不是小王想要的,也不是小明想要的。

阻塞(Blocking)和非阻塞(Non-Blocking)

阻塞和非阻塞通常用來形容很多線程間的相互影響。比如一個線程占用了臨界區資源,那麼其他所有需要這個資源的線程就必須在這個臨界區中等待。等待會導致線程掛起,這種情況就是阻塞。此時,如果占用資源的線程一直不願意釋放資源,那麼其他線程阻塞在這個臨界區上的線程都不能工作。

非阻塞的意思與之相反,它強調沒有一個線程可以妨礙其他線程執行,所有的線程都會嘗試不斷向前執行。

死鎖(Deadlock)、饑餓(Starvation)和活鎖(Livelock)

死鎖饑餓活鎖都屬於多線程的活躍性問題。如果發現上述幾種情況,那麼相關線程就不再活躍,也就是說它可能很難再繼續往下執行了。

死鎖應該是最糟糕的一種情況了(當然,其他幾種情況也好不到哪裡去),如下圖顯示了一個死鎖的發生:

A、B、C、D四輛小車都在這種情況下都無法繼續行駛了。他們彼此之間相互占用了其他車輛的車道,如果大家都不願意釋放自己的車道,那麼這個狀況將永遠持續下去,誰都不可能通過,死鎖是一個很嚴重的並且應該避免和實時小心的問題,後面的文章中會做更詳細的討論。

饑餓是指某一個或者多個線程因為種種原因無法獲得所要的資源,導致一直無法執行。比如它的優先順序可能太低,而高優先順序的線程不斷搶占它需要的資源,導致低優先順序線程無法工作。在自然界中,母雞給雛鳥喂食很容易出現這種情況:由於雛鳥很多,食物有限,雛鳥之間的事務競爭可能非常厲害,經常搶不到事務的雛鳥有可能被餓死。線程的饑餓非常類似這種情況。此外,某一個線程一直占著關鍵資源不放,導致其他需要這個資源的線程無法正常執行,這種情況也是饑餓的一種。於死鎖想必,饑餓還是有可能在未來一段時間內解決的(比如,高優先順序的線程已經完成任務,不再瘋狂執行)。

活鎖是一種非常有趣的情況。不知道大家是否遇到過這麼一種場景,當你要做電梯下樓時,電梯到了,門開了,這是你正準備出去。但很不巧的是,門外一個人當著你的去路,他想進來。於是,你很禮貌地靠左走,禮讓對方。同時,對方也非常禮貌的靠右走,希望禮讓你。結果,你們倆就又撞上了。於是乎,你們都意識到了問題,希望儘快避讓對方,你立即向右邊走,同時,他立即向左邊走。結果,又撞上了!不過介於人類的智慧,我相信這個動作重覆兩三次後,你應該可以順利解決這個問題。因為這個時候,大家都會本能地對視,進行交流,保證這種情況不再發生。但如果這種情況發生在兩個線程之間可能就不那麼幸運了。如果線程智力不夠。且都秉承著“謙讓”的原則,主動將資源釋放給他人使用,那麼久會導致資源不斷地在兩個線程間跳動,而沒有一個線程可以同時拿到所有資源正常執行。這種情況就是活鎖。

死鎖的例子

package com.jvm.visualvm;

/**
 * <a href="http://www.itsoku.com/archives">Java乾貨鋪子,只生產乾貨,公眾號:javacode2018</a>
 */
public class Demo4 {

    public static void main(String[] args) {
        Obj1 obj1 = new Obj1();
        Obj2 obj2 = new Obj2();
        Thread thread1 = new Thread(new SynAddRunalbe(obj1, obj2, 1, 2, true));
        thread1.setName("thread1");
        thread1.start();
        Thread thread2 = new Thread(new SynAddRunalbe(obj1, obj2, 2, 1, false));
        thread2.setName("thread2");
        thread2.start();
    }

    /**
     * 線程死鎖等待演示
     */
    public static class SynAddRunalbe implements Runnable {
        Obj1 obj1;
        Obj2 obj2;
        int a, b;
        boolean flag;

        public SynAddRunalbe(Obj1 obj1, Obj2 obj2, int a, int b, boolean flag) {
            this.obj1 = obj1;
            this.obj2 = obj2;
            this.a = a;
            this.b = b;
            this.flag = flag;
        }

        @Override
        public void run() {
            try {
                if (flag) {
                    synchronized (obj1) {
                        Thread.sleep(100);
                        synchronized (obj2) {
                            System.out.println(a + b);
                        }
                    }
                } else {
                    synchronized (obj2) {
                        Thread.sleep(100);
                        synchronized (obj1) {
                            System.out.println(a + b);
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class Obj1 {
    }

    public static class Obj2 {
    }
}

運行上面代碼,可以通過jstack查看到死鎖信息:

"thread2" #13 prio=5 os_prio=0 tid=0x0000000029225000 nid=0x3c94 waiting for monitor entry [0x0000000029c9f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:50)
    - waiting to lock <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1)
    - locked <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2)
    at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
    - None

"thread1" #12 prio=5 os_prio=0 tid=0x0000000029224800 nid=0x6874 waiting for monitor entry [0x0000000029b9f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:43)
    - waiting to lock <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2)
    - locked <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1)
    at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
    - None

thread1持有com.jvm.visualvm.Demo4$Obj1的鎖,等待獲取com.jvm.visualvm.Demo4$Obj2的鎖
thread2持有com.jvm.visualvm.Demo4$Obj2的鎖,等待獲取com.jvm.visualvm.Demo4$Obj1的鎖,兩個線程相互等待獲取對方持有的鎖,出現死鎖。

饑餓死鎖的例子

package com.jvm.jconsole;

import java.util.concurrent.*;

/**
 * <a href="http://www.itsoku.com/archives">Java乾貨鋪子,只生產乾貨,公眾號:javacode2018</a>
 */
public class ExecutorLock {
    private static ExecutorService single = Executors.newSingleThreadExecutor();

    public static class AnotherCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("in AnotherCallable");
            return "annother success";
        }
    }


    public static class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("in MyCallable");
            Future<String> submit = single.submit(new AnotherCallable());
            return "success:" + submit.get();
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable task = new MyCallable();
        Future<String> submit = single.submit(task);
        System.out.println(submit.get());
        System.out.println("over");
        single.shutdown();
    }
}

執行代碼,輸出:

in MyCallable

使用jstack命令查看線程堆棧信息:

"pool-1-thread-1" #12 prio=5 os_prio=0 tid=0x0000000028e3d000 nid=0x58a4 waiting on condition [0x00000000297ff000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x0000000717921bf0> (a java.util.concurrent.FutureTask)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
    at java.util.concurrent.FutureTask.get(FutureTask.java:191)
    at com.jvm.jconsole.ExecutorLock$MyCallable.call(ExecutorLock.java:25)
    at com.jvm.jconsole.ExecutorLock$MyCallable.call(ExecutorLock.java:20)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
    - <0x00000007173f2690> (a java.util.concurrent.ThreadPoolExecutor$Worker)

"main" #1 prio=5 os_prio=0 tid=0x00000000033e4000 nid=0x5f94 waiting on condition [0x00000000031fe000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000007173f1d48> (a java.util.concurrent.FutureTask)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
    at java.util.concurrent.FutureTask.get(FutureTask.java:191)
    at com.jvm.jconsole.ExecutorLock.main(ExecutorLock.java:32)

   Locked ownable synchronizers:
    - None

堆棧信息結合圖中的代碼,可以看出主線程在32行處於等待中,線程池中的工作線程在25行處於等待中,等待獲取結果。由於線程池是一個線程,AnotherCallable得不到執行,而被餓死,最終導致了程式死鎖的現象。

java高併發系列交流群:


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

-Advertisement-
Play Games
更多相關文章
  • 緩存鎖  我們常常將緩存作為分散式鎖的解決方案,但是卻不能單純的判斷某個 key 是否存在 來作為鎖的獲得依據,因為無論是 exists 和 get 命名都不是線程安全的,都無法保證只有一個線程可以獲得鎖,存線上程爭搶,可能會有多個線程同時拿到鎖的情況(經典的 Redis “讀後寫”的問題 ...
  • 一、mybatis逆向工程 由官方自動生成dao mapper.xml pojo等文件步驟:1)、導入jar包: mybatis-generator-core-1.3.6 代碼生成器的核心包 mysql-connector-java-5.1.28-bin.jar 連接資料庫 mybatis-3.2. ...
  • SQL映射器Mapper介面 MyBatis基於代理機制,可以讓我們無需再寫Dao的實現。直接把以前的dao介面定義成符合規則的Mapper。 註意事項: 1.介面必須以Mapper結尾,名字是DomainMapper 2.mapper.xml文件要和Mapper介面建立關係,通過namespace ...
  • 1.網上大部分都是這種方法 註釋掉 tomcat 9 安裝目錄下的conf里的 logging.properties 找到 java.util.logging.ConsoleHandler.encoding = UTF-8 將其註釋掉,或改為 GBK 2.第二種方法,修改JAVA預設語言 在大部分w ...
  • python預設參數陷阱 0|1陷阱? 學過函數的人一定聽說過函數的預設參數,關於函數的預設參數,請看以下的例子: def extendList(val, lst=[]): lst.append(val) return lst list1 = extendList(10) list2 = exten ...
  • 前言 之前我們探討過一個.class文件是如何被載入到jvm中的。但是jvm內又是如何劃分記憶體的呢?這個內被載入到了那一塊記憶體中?jvm記憶體劃分也是面試當中必被問到的一個面試題。 什麼是jvm記憶體區域劃分? 其實這個問題非常簡單,JVM在運行我們寫好的代碼時,他是必須使用多塊記憶體空間的,不同的記憶體空 ...
  • //表單@if($v['sex']==0) <td class="se" ss="{{$v['sex']}}" id="{{$v['id']}}" >男</td> @elseif($v['sex']==1) <td class="se" ss="{{$v['sex']}}" id="{{$v['id ...
  • 一、依賴註入的概念瞭解 介紹依賴註入(DI),首先要先瞭解一個概念——即控制反轉(IoC)。 控制反轉是面向對象編程的一種設計原則,可以用來減低電腦代碼之間的耦合度。在傳統的應用程式中,都是程式員手動在類的內部創建需要依賴的對象,而這種方式經常會導致類與類之間的高度耦合,難以測試。而當有了IoC容 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...