脫發(fā)篇-多線程基礎(下) 朋友 來看看你知道多少

看完了,發(fā)現對你有用的話點個贊吧! 持續(xù)努力更新學習中??!多線程其他的部分點擊我的頭像查看更多哦!

知識點

image.png

標注:在學習中需要修改的內容以及筆記全在這里 www.javanode.cn,謝謝!有任何不妥的地方望糾正

線程創(chuàng)建

1. 創(chuàng)建方式

  • 繼續(xù)Thread類
  • 實現Runable接口
  • 實現Callable接口,并與Future、線程池結合使用,
1. 繼承Thread
   Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("this is new thread");
            }
        };
        thread.start();
2. 實現runable接口
 Thread thread1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("impl runnable thread");
            }
        });
 thread1.start();
3. 實現Callable接口
   /**
         * 3.實現callable接口,提交給ExecutorService返回的是異步執(zhí)行的結果
         */
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<String> submit = executorService.submit(new Callable<String>() {
            public String call() throws Exception {
                return "three new callable thread";
            }
        });
        String returnString = submit.get();
        System.out.println(returnString);

2. 總結

  • 實現Runnable接口比繼承Thread類所具有的優(yōu)勢:

1):適合多個相同的程序代碼的線程去處理同一個資源

2):可以避免java中的單繼承的限制

3):增加程序的健壯性,代碼可以被多個線程共享,代碼和數據獨立

4):線程池只能放入實現Runable或callable類線程,不能直接放入繼承Thread的類

線程狀態(tài)切換

image.png
  1. 新建狀態(tài)(New):新創(chuàng)建了一個線程對象。

  2. 就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中,變得可運行,等待獲取CPU的使用權。

  3. 運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。

  4. 阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉到運行狀態(tài)。阻塞的情況分三種:

  • 等待阻塞:運行的線程執(zhí)行wait()方法,JVM會把該線程放入等待池中。(wait會釋放持有的鎖)

  • 同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。

  • 其他阻塞:運行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態(tài)。(注意,sleep是不會釋放持有的鎖)

  1. 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結束生命周期。

線程調度

Java線程的實現:Java線程模型是基于操作系統(tǒng)原生線程模型來實現的;
線程模型只對線程的并發(fā)規(guī)模和操作成本產生影響,對Java程序的編寫和運行過程來說,并沒有什么不同。

1. 線程優(yōu)先級

時分形式是現代操作系統(tǒng)采用的基本線程調度形式,操作系統(tǒng)將CPU資源分為一個個的時間片,并分配給線程,線程使用獲取的時間片執(zhí)行任務,時間片使用完之后,操作系統(tǒng)進行線程調度,其他獲得時間片的線程開始執(zhí)行;那么,一個線程能夠分配得到的時間片的多少決定了線程使用多少的處理器資源,線程優(yōu)先級則是決定線程可以獲得多或少的處理器資源的線程屬性;

可以通過設置線程的優(yōu)先級,使得線程獲得處理器執(zhí)行時間的長短有所不同,但采用這種方式來實現線程獲取處理器執(zhí)行時間的長短并不可靠(因為系統(tǒng)的優(yōu)先級和Java中的優(yōu)先級不是一一對應的,有可能Java中多個線程優(yōu)先級對應于系統(tǒng)中同一個優(yōu)先級);Java中有10個線程優(yōu)先級,從1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY),默認優(yōu)先級為5;因此,程序的正確性不能夠依賴線程優(yōu)先級的高低來判斷;

2. 線程調度分類

線程調度是指系統(tǒng)為線程分配處理器使用權的過程;主要調度方式有:搶占式線程調度、協(xié)同式線程調度

2.1 搶占式線程調度

每個線程由系統(tǒng)來分配執(zhí)行時間,線程的切換不由線程本身決定;Java默認使用的線程調度方式是搶占式線程調度;我們可以通過Thread.yield()使當前正在執(zhí)行的線程讓出執(zhí)行時間,但是,卻沒有辦法使線程去獲取執(zhí)行時間;

2.2 協(xié)同式線程調度

每個線程的執(zhí)行時間由線程本身來控制,線程執(zhí)行完任務后主動通知系統(tǒng),切換到另一個線程上;

2.3 兩種線程調度方式的優(yōu)缺點

協(xié)同式的優(yōu)點:實現簡單,可以通過對線程的切換控制避免線程安全問題;
協(xié)同式的缺點:一旦當前線程出現問題,將有可能影響到其他線程的執(zhí)行,最終可能導致系統(tǒng)崩潰;
搶占式的優(yōu)點:一個線程出現問題不會影響到其他線程的執(zhí)行(線程的執(zhí)行時間是由系統(tǒng)分配的,因此,系統(tǒng)可以將處理器執(zhí)行時間分配給其他線程從而避免一個線程出現故障導致整個系統(tǒng)崩潰的現象發(fā)生)

2.4 結論

Java中,線程的調度策略主要是搶占式調度策略,正是因為搶占式調度策略,導致多線程程序執(zhí)行過程中,實際的運行過程與我們邏輯上理解的順序存在較大的區(qū)別,也就是多線程程序的執(zhí)行具有不確定性,從而會導致一些線程安全性問題的發(fā)生;

3. 調度方式

3.1 調度的方式

  1. 線程睡眠:Thread.sleep(long millis)方法,使線程轉到阻塞狀態(tài)。millis參數設定睡眠的時間,以毫秒為單位。當睡眠結束后,就轉為就緒(Runnable)狀態(tài)。sleep()平臺移植性好。
  2. 線程等待:Object類中的wait()方法,導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價于調用 wait(0) 一樣。
  3. 線程讓步:Thread.yield() 方法,暫停當前正在執(zhí)行的線程對象,把執(zhí)行機會讓給相同或者更高優(yōu)先級的線程。
  4. 線程加入:join()方法,等待其他線程終止。在當前線程中調用另一個線程的join()方法,則當前線程轉入阻塞狀態(tài),直到另一個進程運行結束,當前線程再由阻塞轉為就緒狀態(tài)。
  5. 線程喚醒:Object類中的notify()方法,喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,并在對實現做出決定時發(fā)生。線程通過調用其中一個 wait 方法,在對象的監(jiān)視器上等待。

3.2 深入理解(重要)

sleep()

sleep(long millis): 在指定的毫秒數內讓當前正在執(zhí)行的線程休眠(暫停執(zhí)行)

join()

join():指等待t線程終止。

join是Thread類的一個方法,啟動線程后直接調用,即join()的作用是:等待該線程終止,也就是在子線程調用了join()方法后面的代碼,只有等到子線程結束了才能執(zhí)行。

案例

在很多情況下,主線程生成并啟動了子線程,如果子線程里要進行大量的耗時的運算,主線程往往將于子線程之前結束,但是如果主線程處理完其他的事務后,需要用到子線程的處理結果,也就是主線程需要等待子線程執(zhí)行完成之后再結束,這個時候就要用到join()方法了。

代碼:

package cn.javanode.thread.joinUse;

/**
 * @author xgt(小光頭)
 * @version 1.0
 * @date 2021-1-10 9:52
 */
public class JoinUseRunnableThread {

   static class joinThrad implements Runnable{
       @Override
       public void run() {
           System.out.println(Thread.currentThread().getName() +" 線程運行開始!");
           for (int i = 0; i < 5; i++) {
               System.out.println("子線程"+Thread.currentThread().getName() +"運行 : "+i);
               try {
                   Thread.sleep((int)Math.random()*10);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }

           }

   }
   }



    public static void main(String[] args) {
        System.out.println("main方法的線程開啟");

        Thread joinThread = new Thread(new joinThrad());
        joinThread.setName("JoinThread");
        joinThread.start();
        //添加join 子線程調用了join()方法后面的代碼,只有等到子線程結束了才能執(zhí)行。
        try {
            joinThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main方法的線程結束");
    }
}

yield()

yield():暫停當前正在執(zhí)行的線程對象,并執(zhí)行其他線程

yield()做的是讓當前運行線程回到可運行狀態(tài),以允許具有相同優(yōu)先級的其他線程獲得運行機會。因此,使用yield()的目的是讓相同優(yōu)先級的線程之間能適當的輪轉執(zhí)行。但是,實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程調度程序再次選中。

結論:yield()從未導致線程轉到等待/睡眠/阻塞狀態(tài)。在大多數情況下,yield()將導致線程從運行狀態(tài)轉到可運行狀態(tài),但有可能沒有效果。

package cn.javanode.thread.yieldUse;
/**
 * @author xgt(小光頭)
 * @version 1.0
 * @date 2021-1-10 10:57
 */
public class ThreadYieldDemo {

    static class yieldThread implements Runnable{

        @Override
        public void run() {

            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName()+"runing time="+i);

                if(i==30){
                    Thread.yield();
                }

            }
        }
    }

    public static void main(String[] args) {
        //yield()從未導致線程轉到等待/睡眠/阻塞狀態(tài)。在大多數情況下,yield()將導致線程從運行狀態(tài)轉到可運行狀態(tài),但有可能沒有效果。
        Thread yt1 = new Thread(new yieldThread());
        yt1.setName("ytthread1");

        Thread yt2 = new Thread(new yieldThread());
        yt2.setName("ytthread2");


        yt1.start();
        yt2.start();

    }

}

3.3 補充

sleep()和yield()的區(qū)別
    • sleep()使當前線程進入停滯狀態(tài),所以執(zhí)行sleep()的線程在指定的時間內肯定不會被執(zhí)行;
    • yield()只是使當前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。

補充:

sleep 方法使當前運行中的線程睡眠一段時間,進入不可運行狀態(tài),這段時間的長短是由程序設定的,yield 方法使當前線程讓出 CPU 占有權,但讓出的時間是不可設定的。實際上,yield()方法對應了如下操作:先檢測當前是否有相同優(yōu)先級的線程處于同可運行狀態(tài),如有,則把 CPU 的占有權交給此線程,否則,繼續(xù)運行原來的線程。所以yield()方法稱為“退讓”,它把運行機會讓給了同等優(yōu)先級的其他線程

    • sleep 方法允許較低優(yōu)先級的線程獲得運行機會,
    • yield() 方法執(zhí)行時,當前線程仍處在可運行狀態(tài),所以,不可能讓出較低優(yōu)先級的線程時獲得 CPU 占有權。

補充:

在一個運行系統(tǒng)中,如果較高優(yōu)先級的線程沒有調用 sleep 方法,又沒有受到 I\O 阻塞,那么,較低優(yōu)先級線程只能等待所有較高優(yōu)先級的線程運行結束,才有機會運行。

wait和sleep區(qū)別

共同點:

  1. 多線程的環(huán)境下,都可以在程序的調用處阻塞指定的毫秒數,并返回。

  2. wait()和sleep()都可以通過interrupt()方法 打斷線程的暫停狀態(tài) ,從而使線程立刻拋出InterruptedException。

如果線程A希望立即結束線程B,則可以對線程B對應的Thread實例調用interrupt方法。如果此刻線程B正在wait/sleep /join,則線程B會立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結束線程。

需要注意的是,InterruptedException是線程自己從內部拋出的,并不是interrupt()方法拋出的。對某一線程調用 interrupt()時,如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會拋出InterruptedException。但是,一旦該線程進入到 wait()/sleep()/join()后,就會立刻拋出InterruptedException 。

不同點:

  1. 所屬對象不同Thread類的方法:sleep(),yield()等 。Object對象的方法:wait()和notify()
  2. 是否釋放鎖:每個對象都有一個鎖來控制同步訪問。Synchronized關鍵字可以和對象的鎖交互,來實現線程的同步。sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。sleep()睡眠時,保持對象鎖,仍然占有該鎖;wait()睡眠時,釋放對象鎖。但是wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀態(tài),從而使線程立刻拋出InterruptedException(但不建議使用該方法)。
  3. 使用的位置不同wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用
  4. 異常捕獲:sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常

補充:

sleep()方法

sleep()使當前線程進入停滯狀態(tài)(阻塞當前線程),讓出CUP的使用、目的是不讓當前線程獨自霸占該進程所獲的CPU資源,以留一定時間給其他線程執(zhí)行的機會;
sleep()是Thread類的Static(靜態(tài))的方法;因此他不能改變對象的機鎖,所以當在一個Synchronized塊中調用Sleep()方法時,線程雖然休眠了,但是對象的機鎖并木有被釋放,其他線程無法訪問這個對象(即使睡著也持有對象鎖)。在sleep()休眠時間期滿后,該線程不一定會立即執(zhí)行,這是因為其它線程可能正在運行而且沒有被調度為放棄執(zhí)行,除非此線程具有更高的優(yōu)先級。

wait())方法

wait()方法是Object類里的方法;當一個線程執(zhí)行到wait()方法時,它就進入到一個和該對象相關的等待池中,同時失去(釋放)了對象的機鎖(暫時失去機鎖,wait(long timeout)超時時間到后還需要返還對象鎖);其他線程可以訪問;wait()使用notify或者notifyAlll或者指定睡眠時間來喚醒當前等待池中的線程。
wiat()必須放在synchronized block中,否則會在program runtime時扔出”java.lang.IllegalMonitorStateException“異常。

wait()和notify()、notifyAll()

這三個方法用于協(xié)調多個線程對共享數據的存取,所以必須在synchronized語句塊內使用。synchronized關鍵字用于保護共享數據,阻止其他線程對共享數據的存取,但是這樣程序的流程就很不靈活了,如何才能在當前線程還沒退出synchronized數據塊時讓其他線程也有機會訪問共享數據呢?此時就用這三個方法來靈活控制。wait() 方法使當前線程暫停執(zhí)行并釋放對象鎖標示,讓其他線程可以進入synchronized數據塊,當前線程被放入對象等待池中。當調用notify()方法后,將從對象的等待池中移走一個任意的線程并放到鎖標志等待池中,只有鎖標志等待池中線程能夠獲取鎖標志;如果鎖標志等待池中沒有線程,則notify()不起作用。notifyAll() 從對象等待池中移走所有等待那個對象的線程并放到鎖標志等待池中。(下面的線程間通信部分會細說)

wait,notify 和notifyAll 這些方法為什么不在 thread類里面

Java提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程來獲得 由于 wait notify和notifyAll 都是鎖級別的的操作,所以把他們定義在Object類中因為鎖屬于對象。

線程間通信(重要)

如果你的多線程程序僅僅是每個線程獨立完成各自的任務,相互之間并沒有交互和協(xié)作,那么,你的程序是無法發(fā)揮出多線程的優(yōu)勢的,只有有交互的多線程程序才是有意義的程序,否則,還不如使用單線程執(zhí)行多個方法實現程序來的簡單、易懂、有效!

1. java等待通知機制

場景:線程A修改了對象O的值,線程B感知到對象O的變化,執(zhí)行相應的操作,這樣就是一個線程間交互的場景;可以看出,這種方式,相當于線程A是發(fā)送了消息,線程B接收到消息,進行后續(xù)操作,是不是很像生產者與消費者的關系?我們都知道,生產者與消費者模式可以實現解耦,使得程序結構上具備伸縮性;

  • 一種簡單的方式是,線程B每隔一段時間就輪詢對象O是否發(fā)生變化,如果發(fā)生變化,就結束輪詢,執(zhí)行后續(xù)操作;

缺點: 這種方式不能保證對象O的變更及時被線程B感知,同時,不斷地輪詢也會造成較大的開銷;分析這些問題的癥結在哪?其實,可以發(fā)現狀態(tài)的感知是拉取的,而不是推送的,因此才會導致這樣的問題產生

  • Java內置的經典的等待/通知機制

那就是wait()/notify()/notifyAll(),重要 便于理解例子

    /**  
    如果在調用了此方法之后,其他線程調用notify()或者notifyAll()方法之前,線程被中斷,則會清除中斷標志并拋出異常
     * 當前線程必須擁有對象O的監(jiān)視器,調用了對象O的此方法會導致當前線程釋放已占有的監(jiān)視器,并且等待
     * 其它線程對象O的notify()或者notifyAll()方法,當其它線程執(zhí)行了這兩個方法中的一個之后,并且
     * 當前線程獲取到處理器執(zhí)行權,就可以嘗試獲取監(jiān)視器,進而繼續(xù)后續(xù)操作的執(zhí)行
     */
    public final void wait() throws InterruptedException {
        wait(0);
    }
    /**
        喚醒等待在對象O的監(jiān)視器上的一個線程,如果多個線程等待在對象O的監(jiān)視器上,那么將會選擇其中的一個進行喚醒
     * 被喚醒的線程只有在當前線程釋放鎖之后才能夠繼續(xù)執(zhí)行.
     * 被喚醒的線程將會與其他線程一同競爭對象O的監(jiān)視器鎖
     * 這個方法必須在擁有對象O的監(jiān)視器的線程中進行調用
     * 同一個時刻,只能有一個線程擁有該對象的監(jiān)視器
     */
    public final native void notify();
    /**
     *喚醒等待在對象O的監(jiān)視器上的所有線程
     * 被喚醒的線程只有在當前線程釋放鎖之后才能夠繼續(xù)執(zhí)行.
     * 被喚醒的線程將會與其他線程一同競爭對象O的監(jiān)視器鎖
     * 這個方法必須在擁有對象O的監(jiān)視器的線程中進行調用
     * 同一個時刻,只能有一個線程擁有該對象的監(jiān)視器
     */
    public final native void notifyAll();

2. 經典的等待/通知機制代碼

package cn.javanode.thread.JavaWaitAndConsumer;

    public class WaitAndNotify {
        //輪詢標志位
        private static boolean stop = false;
        //監(jiān)視器對應的對象
        private static Object monitor = new Object();
        //等待線程
        static class WaitThread implements Runnable{
            @Override
            public void run() {


                synchronized(monitor){
                    //循環(huán)檢測標志位是否變更
                    while(!stop){
                        try {
                            //標志位未變更,進行等待 鎖釋放,整個線程等待
                            monitor.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //被喚醒后獲取到對象的監(jiān)視器之后執(zhí)行的代碼
                    System.out.println("1Thread "+Thread.currentThread().getName()+" is awakened at first time");
                    stop = false;
                }
                //休眠1秒之后,線程角色轉換為喚醒線程
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //與上述代碼相反的邏輯
                synchronized(monitor){
                    while(stop){
                        try {
                            monitor.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    monitor.notify();
                    stop = true;
                    System.out.println("2Thread "+ Thread.currentThread().getName()+" notifies the waitted thread at first time");
                }
            }
        }
        //通知線程
        static class NotifyThread implements Runnable{
            @Override
            public void run() {
                synchronized (monitor){
                    while(stop){
                        try {
                            monitor.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    stop = true;
                    monitor.notify();
                    System.out.println("3Thread "+ Thread.currentThread().getName()+" notifies the waitted thread at first time");
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (monitor){
                    while(!stop){
                        try {
                            monitor.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("4Thread "+Thread.currentThread().getName()+" is awakened at first time");
                }
            }
        }
        public static void main(String[] args){
            Thread waitThread = new Thread(new WaitThread());
            waitThread.setName("waitThread");
            Thread notifyThread = new Thread(new NotifyThread());
            notifyThread.setName("notifyThread");
            waitThread.start();
            notifyThread.start();
        }
    }


通過上述代碼,可以提煉出等待通知機制的經典模式:

等待方實現步驟:

  • 加鎖同步
  • 條件不滿足,進入等待,被喚醒之后,繼續(xù)檢查條件是否滿足(循環(huán)檢測)
  • 條件滿足,退出循環(huán),繼續(xù)執(zhí)行后續(xù)代碼
    synchronized(obj){
        while(condition不滿足){
            obj.wait();
        }
        //后續(xù)操作
    }

通知方實現步驟:

  • 加鎖同步
  • 條件不滿足,跳過循環(huán)檢測
  • 設置條件并喚醒線程
    synchronized(obj){
        while(condition不滿足){
            obj.wait();
        }
        更新condition
        obj.notify();
        //后續(xù)操作
    }

3. 生產者消費者代碼

package cn.javanode.thread.JavaWaitAndConsumer;
    public class ProducerAndConsumer {
        //商品庫存
        private static int storeMount = 0;
        //監(jiān)視器對應的對象
        private static Object monitor = new Object();
        //生產者線程
        static class ProducerThread implements Runnable{
            @Override
            public void run() {
                try {
                    produce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            public void produce() throws InterruptedException {
                while(true){
                    synchronized(monitor){
                        //循環(huán)檢測庫存是否大于0,大于0表示還有商品可以消費,線程等待消費者消費商品
                        while(storeMount > 0){
                            monitor.wait();
                        }
                        //被喚醒后獲取到對象的監(jiān)視器之后執(zhí)行的代碼
                        System.out.println("Thread "+Thread.currentThread().getName()+" begin produce goods");
                        //生產商品
                        storeMount = 1;
                        //喚醒消費者
                        monitor.notify();
                        Thread.sleep(1000);
                    }
                }
            }
        }
        //消費者線程
        static class ConsumerThread implements Runnable{
            @Override
            public void run() {
                try {
                    consume();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            public void consume() throws InterruptedException {
                while(true){
                    synchronized (monitor){
                        //檢測庫存是否不為0,如果不為0,那么有商品可供消費,否則等待生產者生產商品
                        while(storeMount == 0){
                            monitor.wait();
                        }
                        //消費商品
                        storeMount = 0;
                        //喚醒生產者線程
                        monitor.notify();
                        System.out.println("Thread "+Thread.currentThread().getName()+" begin consume goods");
                        Thread.sleep(1000);
                    }
                }
            }
        }
        public static void main(String[] args){
            Thread producerThread = new Thread(new ProducerThread());
            producerThread.setName("producerThread");
            Thread consumerThread = new Thread(new ConsumerThread());
            consumerThread.setName("consumerThread");
            producerThread.start();
            consumerThread.start();
        }
    }

上述代碼示例演示了一個生產者生產商品和一個消費者消費商品的場景,對于一個生產者多個消費者、多個生產者一個消費者、多個生產者多個消費者等場景,只需要將喚醒的方法換為notifyAll()即可,否則,會出現饑餓現象!

4. 總結

以上就是本文敘述的所有內容,本文首先對于給出Java中線程調度形式,引出多線程編程中需要解決的線程安全問題,并分析線程安全問題,給出解決線程安全問題的常用手段(加鎖同步),最后,結合Java內置的等待通知機制,進行了樣例代碼的展示以及分析,給出了經典的等待通知機制的編程范式,最后,基于等待通知機制給A出了生產者消費者模式的實現樣例,希望本文能給想要學習多線程編程的朋友一點幫助,如有不正確的地方,還望指出,十分感謝!

注意細節(jié)(了解)

  • 線程分類
    • 用戶線程:大多數線程都是用戶線程,用于完成業(yè)務功能
    • 守護線程:支持型線程,主要用于后臺調度以及支持性工作,比如GC線程,當JVM中不存在非守護線程時,JVM將會退出
      • Thread.setDaemon(true)來設置線程屬性為守護線程,該操作必須在線程調用start()方法之前執(zhí)行
      • 守護線程中的finally代碼塊不一定會執(zhí)行,因此不要寄托于守護線程中的finally代碼塊來完成資源的釋放
  • 線程交互的方式
    • join
    • sleep/interrupt
    • wait/notify
  • 啟動線程的方式
    • 只能通過線程對象調用start()方法來啟動線程
    • start()方法的含義是,當前線程(父線程)同步告知虛擬機,只要線程規(guī)劃期空閑,就應該立即啟動調用了start()方法的線程
    • 線程啟動前,應該設置線程名,以便使用Jstack分析程序中線程運行狀況時,起到提示性作用
  • 終止線程的方式
    • 中斷檢測機制
      • 線程通過調用目標線程的interrupt()方法對目標線程進行中斷標志,目標線程通過檢測自身的中斷標志位(interrupted()或isInterrupted())來響應中斷,進行資源的釋放以及最后的終止線程操作;
      • 拋出InterruptedException異常的方法在拋出異常之前,都會將該線程的中斷標志位清除,然后拋出異常
    • suspend()/resume()(棄用)
      • 調用后,線程不會釋放已經占有的資源,容易引發(fā)死鎖問題
    • stop()(棄用)
      • 調用之后不一定保證線程資源的釋放
  • 鎖釋放的情況:
    • 同步方法或同步代碼塊的執(zhí)行結束(正常、異常結束)
    • 同步方法或同步代碼塊鎖對象調用wait方法
  • 鎖不會釋放的情況:
    • 調用Thead類的靜態(tài)方法yield()以及sleep()
    • 調用線程對象的suspend()
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容