溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java高并發(fā)系列 - 第1天:必須知道的幾個(gè)概念

發(fā)布時(shí)間:2020-07-31 13:02:24 來(lái)源:網(wǎng)絡(luò) 閱讀:206 作者:路人甲Java 欄目:編程語(yǔ)言

同步(Synchronous)和異步(Asynchronous)

同步和異步通常來(lái)形容一次方法調(diào)用,同步方法調(diào)用一旦開(kāi)始,調(diào)用者必須等到方法調(diào)用返回后,才能繼續(xù)后續(xù)的行為。異步方法調(diào)用更像一個(gè)消息傳遞,一旦開(kāi)始,方法調(diào)用就會(huì)立即返回,調(diào)用者就可以繼續(xù)后續(xù)的操作。而異步方法通常會(huì)在另外一個(gè)線程中“真實(shí)”地執(zhí)行。整個(gè)過(guò)程,不會(huì)阻礙調(diào)用者的工作。

如圖:

java高并發(fā)系列 - 第1天:必須知道的幾個(gè)概念

上圖中顯示了同步方法調(diào)用和異步方法調(diào)用的區(qū)別。對(duì)于調(diào)用者來(lái)說(shuō),異步調(diào)用似乎是一瞬間就完成的。如果異步調(diào)用需要返回結(jié)果,那么當(dāng)這個(gè)異步調(diào)用真實(shí)完成時(shí),則會(huì)通知調(diào)用者。

打個(gè)比方,比如購(gòu)物,如果你去商場(chǎng)買空調(diào),當(dāng)你到了商場(chǎng)看重了一款空調(diào),你就向售貨員下單。售貨員去倉(cāng)庫(kù)幫你調(diào)配物品。這天你熱的是在不行了,就催著商家趕緊給你送貨,于是你就在商店里面候著他們,直到商家把你和空調(diào)一起送回家,一次愉快的購(gòu)物就結(jié)束了。這就是同步調(diào)用。

不過(guò),如果我們趕時(shí)髦,就坐在家里打開(kāi)電腦,在電腦上訂購(gòu)了一臺(tái)空調(diào)。當(dāng)你完成網(wǎng)上支付的時(shí)候,對(duì)你來(lái)說(shuō)購(gòu)物過(guò)程已經(jīng)結(jié)束了。雖然空調(diào)還沒(méi)有送到家,但是你的任務(wù)已經(jīng)完成了。商家接到你的訂單后,就會(huì)加緊安平送貨,當(dāng)然這一切已經(jīng)跟你無(wú)關(guān)了。你已經(jīng)支付完成,想干什么就能去干什么,出去溜幾圈都不成問(wèn)題,等送貨上門的時(shí)候,接到商家的電話,回家一趟簽收就完事了。這就是異步調(diào)用。

并發(fā)(Concurrency)和并行(Parallelism)

并發(fā)和并行是兩個(gè)非常容易被混淆的概念。他們都可以表示兩個(gè)或者多個(gè)任務(wù)一起執(zhí)行,但是側(cè)重點(diǎn)有所不同。并發(fā)偏重于多個(gè)任務(wù)交替執(zhí)行,而多個(gè)任務(wù)之間有可能還是串行的,而并行是真正意義上的“同時(shí)執(zhí)行”,下圖很好地詮釋了這點(diǎn)。

java高并發(fā)系列 - 第1天:必須知道的幾個(gè)概念

大家排隊(duì)在一個(gè)咖啡機(jī)上接咖啡,交替執(zhí)行,是并發(fā);兩臺(tái)咖啡機(jī)上面接咖啡,是并行。

從嚴(yán)格意義上來(lái)說(shuō),并行的多任務(wù)是真的同時(shí)執(zhí)行,而對(duì)于并發(fā)來(lái)說(shuō),這個(gè)過(guò)程只是交替的,一會(huì)執(zhí)行任務(wù)A,一會(huì)執(zhí)行任務(wù)B,系統(tǒng)會(huì)不停地在兩者之間切換。但對(duì)于外部觀察者來(lái)說(shuō),即使多個(gè)任務(wù)之間是串行并發(fā)的,也會(huì)造成多任務(wù)間并行執(zhí)行的錯(cuò)覺(jué)。

并發(fā)說(shuō)的是在一個(gè)時(shí)間段內(nèi),多件事情在這個(gè)時(shí)間段內(nèi)交替執(zhí)行

并行說(shuō)的是多件事情在同一個(gè)時(shí)刻同事發(fā)生。

實(shí)際上,如果系統(tǒng)內(nèi)只有一個(gè)CPU,而使用多進(jìn)程或者多線程任務(wù),那么真實(shí)環(huán)境中這些任務(wù)不可能是真實(shí)并行的,畢竟一個(gè)CPU一次只能執(zhí)行一條指令,在這種情況下多進(jìn)程或者多線程就是并發(fā)的,而不是并行的(操作系統(tǒng)會(huì)不停地切換多任務(wù))。真實(shí)的并行也只可能出現(xiàn)在擁有多個(gè)CPU的系統(tǒng)中(比如多核CPU)。

臨界區(qū)

臨界區(qū)用來(lái)表示一種公共資源或者說(shuō)共享數(shù)據(jù),可以被多個(gè)線程使用,但是每一次只能有一個(gè)線程使用它,一旦臨界區(qū)資源被占用,其他線程要想使用這個(gè)資源就必須等待。

比如,一個(gè)辦公室里有一臺(tái)打印機(jī),打印機(jī)一次只能執(zhí)行一個(gè)任務(wù)。如果小王和×××同時(shí)需要打印文件,很明顯,如果小王先發(fā)了打印任務(wù),打印機(jī)就開(kāi)始打印小王的文件,×××的任務(wù)就只能等待小王打印結(jié)束后才能打印,這里的打印機(jī)就是一個(gè)臨界區(qū)的例子。

在并行程序中,臨界區(qū)資源是保護(hù)的對(duì)象,如果意外出現(xiàn)打印機(jī)同時(shí)執(zhí)行兩個(gè)任務(wù)的情況,那么最有可能的結(jié)果就是打印出來(lái)的文件是損壞的文件,它既不是小王想要的,也不是×××想要的。

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

阻塞和非阻塞通常用來(lái)形容很多線程間的相互影響。比如一個(gè)線程占用了臨界區(qū)資源,那么其他所有需要這個(gè)資源的線程就必須在這個(gè)臨界區(qū)中等待。等待會(huì)導(dǎo)致線程掛起,這種情況就是阻塞。此時(shí),如果占用資源的線程一直不愿意釋放資源,那么其他線程阻塞在這個(gè)臨界區(qū)上的線程都不能工作。

非阻塞的意思與之相反,它強(qiáng)調(diào)沒(méi)有一個(gè)線程可以妨礙其他線程執(zhí)行,所有的線程都會(huì)嘗試不斷向前執(zhí)行。

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

死鎖饑餓活鎖都屬于多線程的活躍性問(wèn)題。如果發(fā)現(xiàn)上述幾種情況,那么相關(guān)線程就不再活躍,也就是說(shuō)它可能很難再繼續(xù)往下執(zhí)行了。

死鎖應(yīng)該是最糟糕的一種情況了(當(dāng)然,其他幾種情況也好不到哪里去),如下圖顯示了一個(gè)死鎖的發(fā)生:

java高并發(fā)系列 - 第1天:必須知道的幾個(gè)概念

A、B、C、D四輛小車都在這種情況下都無(wú)法繼續(xù)行駛了。他們彼此之間相互占用了其他車輛的車道,如果大家都不愿意釋放自己的車道,那么這個(gè)狀況將永遠(yuǎn)持續(xù)下去,誰(shuí)都不可能通過(guò),死鎖是一個(gè)很嚴(yán)重的并且應(yīng)該避免和實(shí)時(shí)小心的問(wèn)題,后面的文章中會(huì)做更詳細(xì)的討論。

饑餓是指某一個(gè)或者多個(gè)線程因?yàn)榉N種原因無(wú)法獲得所要的資源,導(dǎo)致一直無(wú)法執(zhí)行。比如它的優(yōu)先級(jí)可能太低,而高優(yōu)先級(jí)的線程不斷搶占它需要的資源,導(dǎo)致低優(yōu)先級(jí)線程無(wú)法工作。在自然界中,母雞給雛鳥(niǎo)喂食很容易出現(xiàn)這種情況:由于雛鳥(niǎo)很多,食物有限,雛鳥(niǎo)之間的事務(wù)競(jìng)爭(zhēng)可能非常厲害,經(jīng)常搶不到事務(wù)的雛鳥(niǎo)有可能被餓死。線程的饑餓非常類似這種情況。此外,某一個(gè)線程一直占著關(guān)鍵資源不放,導(dǎo)致其他需要這個(gè)資源的線程無(wú)法正常執(zhí)行,這種情況也是饑餓的一種。于死鎖想必,饑餓還是有可能在未來(lái)一段時(shí)間內(nèi)解決的(比如,高優(yōu)先級(jí)的線程已經(jīng)完成任務(wù),不再瘋狂執(zhí)行)。

活鎖是一種非常有趣的情況。不知道大家是否遇到過(guò)這么一種場(chǎng)景,當(dāng)你要做電梯下樓時(shí),電梯到了,門開(kāi)了,這是你正準(zhǔn)備出去。但很不巧的是,門外一個(gè)人當(dāng)著你的去路,他想進(jìn)來(lái)。于是,你很禮貌地靠左走,禮讓對(duì)方。同時(shí),對(duì)方也非常禮貌的靠右走,希望禮讓你。結(jié)果,你們倆就又撞上了。于是乎,你們都意識(shí)到了問(wèn)題,希望盡快避讓對(duì)方,你立即向右邊走,同時(shí),他立即向左邊走。結(jié)果,又撞上了!不過(guò)介于人類的智慧,我相信這個(gè)動(dòng)作重復(fù)兩三次后,你應(yīng)該可以順利解決這個(gè)問(wèn)題。因?yàn)檫@個(gè)時(shí)候,大家都會(huì)本能地對(duì)視,進(jìn)行交流,保證這種情況不再發(fā)生。但如果這種情況發(fā)生在兩個(gè)線程之間可能就不那么幸運(yùn)了。如果線程智力不夠。且都秉承著“謙讓”的原則,主動(dòng)將資源釋放給他人使用,那么久會(huì)導(dǎo)致資源不斷地在兩個(gè)線程間跳動(dòng),而沒(méi)有一個(gè)線程可以同時(shí)拿到所有資源正常執(zhí)行。這種情況就是活鎖。

死鎖的例子

package com.jvm.visualvm;

/**
 * <a >Java干貨鋪?zhàn)?只生產(chǎn)干貨,公眾號(hào):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 {
    }
}

運(yùn)行上面代碼,可以通過(guò)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的鎖,兩個(gè)線程相互等待獲取對(duì)方持有的鎖,出現(xiàn)死鎖。

饑餓死鎖的例子

package com.jvm.jconsole;

import java.util.concurrent.*;

/**
 * <a >Java干貨鋪?zhàn)?只生產(chǎn)干貨,公眾號(hào):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();
    }
}

執(zhí)行代碼,輸出:

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

java高并發(fā)系列 - 第1天:必須知道的幾個(gè)概念

堆棧信息結(jié)合圖中的代碼,可以看出主線程在32行處于等待中,線程池中的工作線程在25行處于等待中,等待獲取結(jié)果。由于線程池是一個(gè)線程,AnotherCallable得不到執(zhí)行,而被餓死,最終導(dǎo)致了程序死鎖的現(xiàn)象。

java高并發(fā)系列連載中,總計(jì)估計(jì)會(huì)有四五十篇文章,可以關(guān)注公眾號(hào):javacode2018,獲取最新文章。

java高并發(fā)系列 - 第1天:必須知道的幾個(gè)概念

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI