最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
我看誰還不懂多線程之間的通信+基礎(chǔ)入門+實戰(zhàn)教程+詳細(xì)介紹+附源碼

一、多線程之間的通信(Java版本)

1、多線程概念介紹

多線程概念

創(chuàng)新互聯(lián)公司是一家專業(yè)提供南關(guān)企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站設(shè)計、成都網(wǎng)站設(shè)計、H5場景定制、小程序制作等業(yè)務(wù)。10年已為南關(guān)眾多企業(yè)、政府機構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站制作公司優(yōu)惠進行中。

  • 在我們的程序?qū)用鎭碚f,多線程通常是在每個進程中執(zhí)行的,相應(yīng)的附和我們常說的線程與進程之間的關(guān)系。線程與進程的關(guān)系:線程可以說是進程的兒子,一個進程可以有多個線程。但是對于線程來說,只屬于一個進程。再說說進程,每個進程的有一個主線程作為入口,也有自己的唯一標(biāo)識PID,它的PID也就是這個主線程的線程ID。

  • 對于我們的計算機硬件來說,線程是進程中的一部分,也是進程的的實際運作單位,它也是操作系統(tǒng)中的最小運算調(diào)度單位。多線程可以提高CPU的處理速度。當(dāng)然除了單核CPU,因為單核心CPU同一時間只能處理一個線程。在多線程環(huán)境下,對于單核CP來說,并不能提高響應(yīng)速度,而且還會因為頻繁切換線程上下文導(dǎo)致性能降低。多核心CPU具有同時并行執(zhí)行線程的能力,因此我們需要注意使用環(huán)境。線程數(shù)超出核心數(shù)時也會引起線程切換,并且操作系統(tǒng)對我們線程切換是隨機的。

2、線程之間如何通信

引入

  • 對于我們Java語言來說,多線程編程也是它的特性之一。我們需要利用多線程操作同一共享資源,從而實現(xiàn)一些特殊任務(wù)。上面說了,多線程在進行切換時CPU隨機調(diào)度的,假如我們直接運行多個線程操作共享資源的話,勢必會引起一些不可控錯誤因素。
  • 接下來,我們就需要讓這些不可控變?yōu)榭煽?!這個時候就引出了本文的重點線程通信。線程通信就是為了解決多線程對同一共享變量的爭奪。

Java 線程通信的方式

  • 共享內(nèi)存機制
    • 比如說Java的volatile關(guān)鍵字就是基于內(nèi)存屏障解決變量的可見性,從而實現(xiàn)其他線程訪問共享變量都是必須從主存中獲?。▽?yīng)其他線程對變量的更新也得及時的刷新到主存)。
    • synchronized 關(guān)鍵字基于對象鎖這種方式實現(xiàn)線程互斥,可以通知對方有其他的線程正在執(zhí)行這部分代碼。
  • 消息傳遞模式
    • wait() 和 notify()/notifyAll() 等待通知方式實現(xiàn)線程的阻塞就緒狀態(tài)之間的轉(zhuǎn)換。
    • park、unpark
    • join() 阻塞【底層也是依賴wait實現(xiàn)】。
    • interrupt()打斷阻塞狀態(tài)。
    • 管道輸入/輸出。

3、線程通信方法詳細(xì)介紹

主要介紹wait/notify,也有ReentrantLock的Condition條件變量的await/signal,LockSupport的park/unpark方法,也能實現(xiàn)線程之間的通信。主要是阻塞/喚醒通信模式。

首先說明這種方法一般都是作用于調(diào)用方法的所在線程。比如在主線程執(zhí)行wait方法,就是將主線程阻塞了。

wait/notify機制

  • wait()、notify方法在Java中是Object提供給我們的。又因為所有的類都默認(rèn)隱式繼承了Object類,進而我們的每一個對象都具有wait和notify。
    • wait方法含義:一個線程一旦調(diào)用了任意對象obj.wait()方法,它就釋放了所持有的監(jiān)視器對象(obj)上的鎖,并轉(zhuǎn)為非運行狀態(tài)(阻塞)。
    • notify方法含義:一個線程若執(zhí)行obj.notify方法,則隨機喚醒obj對象上監(jiān)視器(操作系統(tǒng)也稱為管程)monitor的阻塞隊列waitset中一個線程。
    • wait和notify方法的使用同時必須配合synchronized關(guān)鍵字使用。同時也需要成對出現(xiàn)。就是說wait和notify必須得在同步代碼塊內(nèi)部使用,大致原因就是需要保證同時只有一個線程可以去執(zhí)行wait,使該線程阻塞。

await/signal

  • 要想使用await/signal首先是需要借用Condition條件變量,要想獲取Condition條件變量,就必須通過ReentrantLock鎖獲取。
  • ReentrantLock和Synchronized類似,都是可重入鎖,并且大多都是當(dāng)做重量級鎖使用。
    • 區(qū)別:ReentrantLock是API層面實現(xiàn)的,我們可以根據(jù)自己隨意調(diào)用定制,但是Synchronized是JVM底層實現(xiàn),我們無需關(guān)心他上鎖解鎖的流程。
  • await/signal使用時需要配合ReentrantLock鎖對象的lock和unlock方法加鎖解鎖。就像wait/notify在synchronized在同步代碼塊中使用一樣。他們都需要保證當(dāng)前線程是唯一執(zhí)行這段邏輯的線程。防止出現(xiàn)多線程造成的線程安全問題。

park/unpark

二、線程通信過程中需要注意的問題

1、喚醒丟失

如果一個線程先于被通知線程調(diào)用wait()前調(diào)用了notify(),等待的線程將錯過這個信號。

  • 喚醒丟失主要是在我們使用wait 和 notify的過程中的時序問題。比如說我們線程二在執(zhí)行某個對象notify的時候,線程一還沒有執(zhí)行該對象的wait方法。那么這次的喚醒就會丟失,我們就不能讓線程二得notify方法起作用,自然而然線程一就不會被喚醒。
  • 舉個例子吧,這就好比我們平常在宿舍每天都會有叫醒服務(wù),但是這次 因為一些原因(通宵···)我一整晚都沒有睡覺,而且當(dāng)?shù)诙煸缟系慕行逊?wù)來的 時候也是醒著的。那么叫醒服務(wù)就會以為你已經(jīng)醒來了,就會視而不見。沒想到吧,叫醒服務(wù)剛走我就躺下來睡著了,所以我錯過了這次叫醒服務(wù)。就能好好的睡覺了。這看起來沒有什么大問題,但是你仔細(xì)想想若是每個睡著的人都需要被叫醒服務(wù)才能醒過來,外加上只有一次叫醒服務(wù)的機會。那么你就可以沉睡萬年了,開心不。
  • 哈哈哈···
  • 這在程序中也是一樣 的,如果錯過notify那么就會一直wait。
    • 所以我們必須預(yù)防這種問題,比如說每隔一段時間去喚醒,也就是隔兩分鐘就去叫醒睡著的人。但是這種缺點就是太累了,對于程序來說是消耗性能和內(nèi)存。實現(xiàn)也簡單就是寫入while循環(huán)體中,不停地嘗試即可。
    • 我們也可以使用一個標(biāo)志位完美的實現(xiàn)。初始化設(shè)置flag=FALSE表示還沒wait,在wait之前將設(shè)置flag=TRUE,在notify之后設(shè)置flag=FALSE。每次notify喚醒之前都判斷flag=true是否已經(jīng)wait,在wait中判斷flag=false是否已經(jīng)notify。

核心代碼演示

  • 首先使用線程池創(chuàng)建線程一使自己進入阻塞態(tài),然后再調(diào)用LOCK1的notify方法喚醒線程一
	    // 線程一使用LOCK1對象調(diào)用wait方法阻塞自己
        executor.execute(new ThreadTest("線程一",LOCK1,LOCK2));

        synchronized (LOCK1) {
            System.out.println("main執(zhí)行notify方法讓線程一醒過來");
            LOCK1.notify();
        }
  • 但是他很有可能醒不來,因為主線程調(diào)用LOCK1對象的notify方法,可能主線程已經(jīng)執(zhí)行完了,上面線程還沒創(chuàng)建完成,也就是沒有進入wait狀態(tài)。就醒不來了。

  • 解決方式:使用信號量標(biāo)志進行判斷是否已經(jīng)進入wait

            synchronized (LOCK1) {
                while (true) {
                    if (FLAG.getFlag()) {
                        System.out.println("main馬上執(zhí)行notify方法讓線程一醒過來" + "flag = " + FLAG.getFlag());
                        LOCK1.notify();
                        // 將標(biāo)志位變?yōu)镕ALSE
                        FLAG.setFlag(Constants.WaitOrNoWait.NO_WAIT.getFlag());
                        System.out.println("main執(zhí)行notify方法完畢" + "flag = " + FLAG.getFlag());
                        break;
                    }
                }
            }
    

2、假喚醒

由于莫名其妙的原因,線程有可能在沒有調(diào)用過notify()和notifyAll()的情況下醒來。

  • 其實在上面的代碼中已經(jīng)解決了假喚醒的問題,因為我們只需要不斷去嘗試獲取標(biāo)志位信息即可。

3、多線程喚醒

  • 多個線程執(zhí)行時,防止notifyAll全部喚醒之后就結(jié)束運行,我們的需求是只能喚醒一個線程,當(dāng)其他線程被喚醒之后需要重新判斷標(biāo)志位是否為FALSE,也就是需要判斷是否有其他線程執(zhí)行了喚醒操作,因為一次只能叫醒一個人,需要排隊,他們就可以繼續(xù)自旋判斷。
		synchronized (waitName) {
            while (!flag.getFlag()) {
                try {
                    // 將標(biāo)志位設(shè)置為TRUE
                    flag.setFlag(Constants.WaitOrNoWait.WAIT.getFlag());
                    System.out.println("name;"+name+" 我睡著了進入阻塞狀態(tài)" + "flag = " + flag.getFlag());
                    waitName.wait();
                    System.out.println("name;"+name+" 我醒來了" + "flag = " + flag.getFlag());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
  • 大家如果使用的是new Thread()方式創(chuàng)建線程的話,要想保證安全的話還可以給該標(biāo)志位加上volatile關(guān)鍵字,可以時刻保證該標(biāo)志位的可見性。
  • 我這里使用的標(biāo)志位是使用傳遞引用的方式,使用同一個對象,將標(biāo)志位定義為該對象中的屬性,然后再結(jié)合枚舉類進行設(shè)置標(biāo)志位的值。因為我使用線程池創(chuàng)建對象,并且自定義線程類,這里是無法設(shè)置全局變量,傳遞給線程類。包裝類也不行哦。(感興趣可以親自試一下)
  • 大體代碼結(jié)構(gòu)如下所示:
	private final static Object LOCK1 = new Object();
    private final static Object LOCK2 = new Object();
    private final  static Constants.WaitStatus FLAG = new Constants.WaitStatus(false);
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 1, TimeUnit.DAYS, new ArrayBlockingQueue<>(4), new ThreadPoolExecutor.AbortPolicy());
        executor.execute(new ThreadTest("線程一",LOCK1,LOCK2, FLAG));
        // ···喚醒
    }

class ThreadTest implements Runnable { //阻塞··· }

完整代碼可以看這[Gitee倉庫完整代碼][https://gitee.com/malongfeistudy/javabase/tree/master/Java多線程_Study/src/main/java/com/mlf/thread/demo_wait_notify]

三、線程通信實戰(zhàn)

前置知識:線程池的使用方法

  • 首先復(fù)習(xí)一下創(chuàng)建線程的幾種方式和其的優(yōu)缺點:

    • 通過new Thread()
    • 繼承Thread():和new Thread沒啥區(qū)別,就是耦合度低了
      • 定義線程類繼承Thread類并且重寫run方法即可。
      • 優(yōu)點是簡潔方便
      • 缺點是占用了該類的單繼承位置,無法繼承其他父類
    • 實現(xiàn)Runnable接口
    • 實現(xiàn)Callable接口
      • 和實現(xiàn)Runnable接口類似
      • 優(yōu)點:
        • 實現(xiàn)接口,不占用繼承的位置;
        • 耦合度降低,并且可定化程度提高。各個模塊之間的調(diào)用關(guān)系更加清晰
      • 缺點:
        • 實現(xiàn)起來稍微麻煩
  • 使用線程池的步驟

    • 線程池初始化方式:
      • 使用Executor初始化線程池
        • 優(yōu)點:方便快捷,適用于自己測試時使用
        • 缺點:在實際開發(fā)中無法判斷細(xì)節(jié)
      • new ThreadPoolExecutor()構(gòu)造器創(chuàng)建(本文使用方式)
        • 優(yōu)點:可以清晰地定制出適合自己的線程池,不會造成資源浪費
        • 缺點:麻煩
  • 在主線程自定義線程池使用實例,這里需要根據(jù)實際情況定義鎖對象,因為我們需要使用這些鎖對象控制多線程之間的運行順序以及線程之間的通信。在Java中每個對象都會在初始化的時候擁有一個監(jiān)視器,我們需要利用好他進行并發(fā)編程。這種創(chuàng)建線程池的方法也是阿里巴巴推薦的方式,想想以阿里的體量多年總結(jié)出來的總沒有錯,大家還是提前約束自己的編碼習(xí)慣等。安裝一個阿里代碼規(guī)范的插件對自己的程序員道路是比較nice的。

    /**
     * 每個使用對應(yīng)唯一的對象作為監(jiān)視器對象鎖。
     */
    public static final Object A_O = new Object();
    public static final Object B_O = new Object(); 
        /** 參數(shù):
         * int corePoolSize,                     核心線程數(shù)
         * int maximumPoolSize,                  最大線程數(shù)
         * long keepAliveTime,                   救急存活時間
         * TimeUnit unit,                        單時間位
         * BlockingQueue workQueue,    阻塞隊列
         * RejectedExecutionHandler handler      拒絕策略
         **/
        // 使用阿里巴巴推薦的創(chuàng)建線程池的方式
        // 通過ThreadPoolExecutor構(gòu)造函數(shù)自定義參數(shù)創(chuàng)建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                3,
                5,
                1,
                TimeUnit.DAYS,
                new ArrayBlockingQueue<>(2),
                new ThreadPoolExecutor.AbortPolicy());
  • 接下來需要自定義線程類,我們可以自定義線程類,并且在該線程類中定義自己需要的共享資源(鎖對象,屬性等),在run方法中寫盡自己的線程運行邏輯即可。
class ThreadDiy implements Runnable {

    private final String name;

    /**
     * 阻塞鎖對象  等待標(biāo)記
     **/
    private final Object waitFor;

    /**
     * 執(zhí)行鎖對象  下一個標(biāo)記
     **/
    private final Object next;

    public AlternateThread(String name, Object waitFor, Object next) {
    }

    @Override
    public void run() {
        // 線程的代碼邏輯···
    }

}

1、控制兩個線程之間的執(zhí)行順序

題目:現(xiàn)在有兩個線程,不論線程的啟動順序,我需要指定線程一先執(zhí)行,然后線程二再執(zhí)行。

  • 初始化兩個對象鎖作為線程監(jiān)視器。

        private final static Object ONE_LOCK = new Object();
        private final static Object TWO_LOCK = new Object();
    
  • 接下來初始化線程池,上面有具體的介紹,在這就不多說了

  • 使用線程池去執(zhí)行我們的兩個線程,在這里我們需要分析的是

        // 使用線程池創(chuàng)建線程
        executor.execute(new DiyThread(1, ONE_LOCK, TWO_LOCK));
        executor.execute(new DiyThread(2, TWO_LOCK, ONE_LOCK));

        synchronized (ONE_LOCK) {
            ONE_LOCK.notify();
        }

創(chuàng)建線程類

  • 我們使用繼承Runnable的方式去創(chuàng)建線程對象,需要在這個類中實現(xiàn)每個線程執(zhí)行的邏輯,我們根據(jù)題目可以得出,我們要控制每個線程的執(zhí)行順序,怎么辦?那么就要實現(xiàn)所有線程之間的通信,通信方式采用wait-notify的方式即可。我們使用wait-notify的時候必須結(jié)合synchronized,那么就需要控制兩個對象鎖。因為我們不光是控制自己,還有另一個線程。

  • 我們再分析一下題意,首先需要指定先后執(zhí)行的順序,那么就需要實現(xiàn)兩個線程之間的通信。其次呢,我們得控制兩個線程,那么就需要兩個監(jiān)視器去監(jiān)視這兩個線程。

  • 我們定義這兩個監(jiān)視器對象為own和other。然后再新增一個屬性threadId來標(biāo)識自己。

        private final int threadId;
        private final Object own;
        private final Object other;
    
  • 接下來就是編寫Run方法了

  • 每個線程首先需要阻塞自己,等待喚醒。然后喚醒之后,再去喚醒另外一個線程。這樣就實現(xiàn)了自定義順序。至于先喚醒哪個線程,交給我們的主線程去完成。

  • 這里需要注意的是,如果我們只是單純地執(zhí)行了多個線程對象,但是主線程沒有主動去喚醒其中一個,這樣就會形成類似于死鎖的循環(huán)等待。你需要我喚醒,我需要你喚醒。這個時候需要主線程去插手喚醒其中的任意一個線程。

    • 第一步阻塞自己own

              synchronized (own) {
                  try {
                      own.wait();
                      System.out.println(num);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
      
    • 第二步喚醒other

              synchronized (other) {
                  other.notify();
              }
      

2、多線程交替打印輸出

題目需求:現(xiàn)在需要使用三個線程輪流打印輸出。說白了也就是多線程輪流執(zhí)行罷了,和問題一控制兩個線程打印順序沒什么區(qū)別

  • 還是老步驟,首先需要定義線程類,我們需要控制當(dāng)前線程和下一個線程即可。我們這里需要兩個對象,一個是阻塞鎖對象用來阻塞當(dāng)前線程。另一個是喚醒鎖對象,用來喚醒下一個對象。
    /**
     * 阻塞鎖對象  等待標(biāo)記
     **/
    private final Object waitFor;
    /**
     * 喚醒鎖對象  下一個標(biāo)記
     **/
    private final Object next;
  • run方法的邏輯和上面的基本一樣。 一個線程一旦調(diào)用了任意對象的wait()方法,它就釋放了所持有的監(jiān)視器對象上的鎖,并轉(zhuǎn)為非運行狀態(tài)。

  • 每個線程首先會調(diào)用 waitFor對象的 wait()方法,隨后該線程進入阻塞狀態(tài),等待其他線程執(zhí)行自己引用的該 waitFor對象的 notify()方法即可。

    		while (true) {
                synchronized (waitFor) {
                    try {
                        waitFor.wait();
                        System.out.println(name + " 開始執(zhí)行");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                synchronized (next) {
                    next.notify();
                }
            }
    
  • 主線程需要初始化線程池、執(zhí)行三個線程,并且最后需要打破僵局,因為此時每個線程都是阻塞狀態(tài),他們沒法阻塞/喚醒循環(huán)下去。

            synchronized (A_O) {
                A_O.notify();
            }
    
  • 模擬執(zhí)行流程

/**
 * 模擬執(zhí)行流程
 * 打印名(name)    等待標(biāo)記(waitFor)   下一個標(biāo)記(next)
 *      1                 A                  B
 *      2                 B                  C
 *      3                 C                  A
 * 
 * 像不像Spring的循環(huán)依賴:確實很像,Spring中的循環(huán)依賴就是 BeanA 依賴 BeanB,BeanB 依賴 BeanA;
 * 他們實例化過程中都需要先屬性注入對方的實例,倘若剛開始的時候都沒有實例化,初始化就會死等。類似于死鎖。
 **/

3、多線程順序打印同一個自增變量

使用多線程輪流打印 01234····

  • 思路:使用自增原子變量AtomicInteger和多線程配合打印。

具體代碼請移步到Gitee倉庫:[順序打印自增變量][https://gitee.com/malongfeistudy/javabase/blob/master/Java多線程_Study/src/main/java/com/mlf/thread/print/AddNumberPrint2.java]

條件變量Condition的使用

  • Condition是一個 LOCK 實例出來的,他們獲取的都是一個 LOCK 的鎖,而如果要調(diào)用 object的 wait和notify 方法,首先要獲取對應(yīng)的object的鎖,如果要調(diào)用Condition 的await、signal方法,必須先獲取Lock鎖(Lock.lock)。
  • 多線程的初衷就是操作共享資源,然后我們需要保證共享資源同一時刻只能被一個線程所修改。那么就需要一把鎖來控制這些線程之間互斥條件。這里使用一個ReentrantLock鎖作為我們的Lock對象。通過同一個 Lock鎖 獲取的每個Condition 就可以作為每個線程自己的阻塞條件和喚醒條件。

如有問題,請留言評論。


新聞標(biāo)題:我看誰還不懂多線程之間的通信+基礎(chǔ)入門+實戰(zhàn)教程+詳細(xì)介紹+附源碼
標(biāo)題來源:http://fisionsoft.com.cn/article/dsoidso.html