多線程(一)、基礎概念及notify()和wait()的使用


1、基礎概念1.一、CPU核心數和線程數的關係1.二、時間片輪起色制 (RR 調度)1.三、進程和線程1.3.一、什麼是進程?1.3.二、什麼是線程?1.四、 併發與並行1.4.一、什麼是併發1.4.二、什麼是並行1.五、同步與異步1.5.一、什麼是同步1.5.二、什麼是異步2、多線程使用2.一、建立多線程2.1.一、實現Runnable接口2.1.二、繼承Thread類2.1.三、實現Callable接口2.二、終止線程2.2.一、 天然終止2.2.二、手動終止3、線程之間共享和協做3.一、 線程之間共享3.二、線程之間的協做3.2.一、 nitify()、notifyAll()、wait() 等待/通知機制java

1、基礎概念

1.一、CPU核心數和線程數的關係

多核心 :單芯片多處理器( Chip Multiprocessors,簡稱CMP),其思想是將大規模並行處理器中的SMP(對稱多處理器)集成到同一芯片內,各個處理器並行執行不一樣的進程。這種依靠多個CPU同時並行地運行程序是實現超高速計算的一個重要方向,稱爲並行處理,web

多線程 :讓同一個處理器上的多個線程同步執行共享處理器的執行資源,可最大限度地實現寬發射、亂序的超標量處理,提升處理器運算部件的利用率,緩和因爲數據相關或 Cache未命中帶來的訪問內存延時。安全

兩者關係 : 目前CPU基本都是多核,不多看到單核CPU。增長核心數目就是爲了增長線程數,由於操做系統是經過線程來執行任務的,一般狀況下它們是1:1對應關係,也就是說四核CPU通常擁有四個線程。但Intel引入超線程技術後,使核心數與線程數造成1:2的關係.網絡

1.二、時間片輪起色制 (RR 調度)

定義:系統把全部就緒進程先入先出的原則排成一個隊列。新來的進程加到就緒隊列末尾。每當執行進程調度時,進程調度程序老是選出就緒隊列的隊首進程,讓它在CPU上運行一個時間片的時間。時間片是一個小的時間單位,一般爲10~100ms數量級。當進程用完分給它的時間片後,系統的計時器發出時鐘中斷,調度程序便中止該進程的運行,把它放入就緒隊列的末尾;而後,把CPU分給就緒隊列的隊首進程,一樣也讓它運行一個時間片,如此往復。多線程

根據上面CPU核心數和線程數的關係 1:1的關係,若是咱們手機是雙核手機,那麼咱們按道理只能起兩個線程,可是在實際的開發過程當中並非這樣,咱們可能開了十幾個線程 "同時" 在執行,這是由於操做系統提供了CPU時間片輪轉這個機制,它爲每一個進程分配一個時間段(即時間片),讓他們在一段時間內交替執行。併發

上下文切換時間:因爲時間片輪轉進制,會使得進程之間不停的進行切換,進程之間切換涉及到保存和裝入到寄存器值及內存映像,更新表格及隊列,這個過程是須要消耗時間的。app

時間片時間設置: 時間片若是設置過短,會致使過多進程不斷切換,因爲切換過程會產生上小文切換時間,因此下降CPU效率,設置太長,又會致使相對較短的交互請求響應變差,一般時間片設置在100ms左右比較合理。less

1.三、進程和線程

1.3.一、什麼是進程?

進程是程序運行資源分配的最小單元異步

進程是操做系統進行資源分配和調度的獨立單元,資源包括CPU,內存空間,磁盤IO等等,同一個進程的全部線程共享該進程的所有資源進程與進程之間相互獨立ide

1.3.二、什麼是線程?

線程是CPU調度的最小單位,必須依賴進程而存在。

線程是進程的實體,是CPU調度和分派的基本單位,線程基本不擁有系統資源,可是擁有程序計數器、一組寄存器、棧等運行中不可少的資源,同一個進程中的線程共享進程所擁有的所有資源

1.四、 併發與並行

1.4.一、什麼是併發

併發是指一個時間段內,有幾個程序都在同一個CPU上運行,但任意一個時刻點上只有一個程序在處理機上運行。

多個線程 一個CPU

跟時間掛鉤,單位時間內。

1.4.二、什麼是並行

並行是指一個時間段內,有幾個程序都在幾個CPU上運行,任意一個時刻點上,有多個程序在同時運行,而且多道程序之間互不干擾。

多個線程 多個CPU

1.五、同步與異步

1.5.一、什麼是同步

同步:在發出一個同步調用時,在沒有獲得結果以前,該調用就不返回,直到結果的返回。

比如我給朋友打電話,你要不接電話,我就一直打,這個過程啥也不幹,就給你打電話,打到你接電話爲止。

1.5.二、什麼是異步

異步:在發出一個異步調用後,調用者不會馬上獲得結果,該調用就返回了。

一樣打電話,我先給你發個消息,告訴我有事找你,而後我就去幹我本身的事情去了,等你看到消息給我回電話,固然,你也能夠不回我電話。

2、多線程使用

2.一、建立多線程

2.1.一、實現Runnable接口
    public static class newRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Runnable");
        }
    }
複製代碼

調用:

new Thread(new newRunnable()).start();
複製代碼
2.1.二、繼承Thread類
    public static class newThread extends Thread {
        @Override
        public void run() {
            super.run();
            System.out.println("newThread");
        }
    }
複製代碼

調用:

new newThread().start();
複製代碼

一、Thread 是java裏面對線程的抽象概念,咱們經過new thread的時候,其實只是建立了一個thread實例,操做系統並無和該線程掛鉤,只有執行了start方法後,纔是真正意義上啓動了線程。

二、start() 會讓一個線程進入就緒隊列等待分配CPU,分到CPU後才調用 run() 方法,

三、start() 方法不能重複調用,不然會拋出 IllegalThreadStateException 異常。

2.1.三、實現Callable接口

Callable接口 是在Java1.5開始提供,能夠在任務執行結束後提供返回值。

public interface Callable<V{
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */

    call() throws Exception;
}
複製代碼

從源碼能夠看到,CallableRunnable 對比來看,不一樣點就在其call方法提供了返回值和進行異常拋出

使用:

public static class newCallable implements Callable<String{
        @Override
        public String call() throws Exception {
            System.out.println("newCallable");
            Thread.sleep(3000);
            return "java1.5後提供,可在任務執行結束返回相應結果";
        }
    }
複製代碼

對Callable的調用須要 FutureTask 這個類,這個類也是 Java1.5 之後提供

        FutureTask<String> futureTask = new FutureTask<>(new newCallable());
        futureTask.run();
        String result = null;
        try {
            result = futureTask.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
複製代碼

執行結果:

能夠看到,咱們經過 FutureTaskget 方法 ,get 方法會進行阻塞,直到任務結束後,纔將返回值進行返回。

咱們能夠簡單看看 FutureTask ,它最終繼承至 Future 接口,FutureTask 除了上面說到的get方法,它還提供瞭如下這些方法,能夠判斷任務是否完成,以及對任務進行取消操做。

  • 一、boolean cancel(boolean mayInterruptIfRunning)

嘗試取消當前任務的執行。若是任務已經取消、已經完成或者其餘緣由不能取消,嘗試將失敗。返回false.

若是任務尚未啓動就調用了cancel(true),任務將永遠不會被執行。

若是任務已經啓動,會根據參數值決定任務是否應該中斷執行該任務的線程.

true: 嘗試中斷該任務,返回 True。
false: 不會中斷任務,繼續執行,返回 True。

  • 二、boolean isCancelled()
    若是任務在正常結束以前被被取消返回true

  • 三、boolean isDone()
    正常結束、異常或者被取消致使任務完成,將返回true

  • 四、V get()
    等待任務結束,而後獲取結果,若是任務在等待過程當中被終端將拋出InterruptedException,若是任務被取消將拋出CancellationException,若是任務中執行過程當中發生異常將拋出ExecutionException。

  • 五、V get(long timeout, TimeUnit unit)
    任務最多在給定時間內完成並返回結果,若是沒有在給定時間內完成任務將拋出TimeoutException。

2.二、終止線程

2.2.一、 天然終止

線程任務執行完成,則這個線程進行終止。

2.2.二、手動終止

暫停、恢復和中止操做對應在線程Thread的API就是suspend()、resume()和stop()。可是這些API是過時的,也就是不建議使用的,主要緣由是方法的調用不能保證線程資源的正常釋放,容易引發其餘反作用的產生。

suspend() :在調用後,線程不會釋放已經佔有的資源(好比鎖),而是佔有着資源進入睡眠狀態,這樣容易引起死鎖問題

stop() : 終結一個線程時不會保證線程的資源正常釋放,一般是沒有給予線程完成資源釋放工做的機會,所以會致使程序可能工做在不肯定狀態下

真正安全的終止線程使用 interrupt() 方法

因爲線程之間是協做式工做,因此在其餘線程使用 interrupt() 終止某個線程時候,這個線程並不會當即終止,只是收到了終止通知,經過檢查自身的中斷標誌位是否爲被置爲 True 再進行相應的操做,固然,這個線程徹底能夠不用理會。

中斷標誌位的判斷:

一、isInterrupted()

判斷線程是否中斷,若是該線程已被中斷則返回 true ,不然返回 false ,該方法不更改中斷標誌位。

    /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */

    public boolean isInterrupted() {
        return isInterrupted(false);
    }
複製代碼

isInterrupted 示例:

private static class UseThread extends Thread{
        public UseThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+" interrupt start flag  ="+isInterrupted());
            while(!isInterrupted()){
                System.out.println(threadName+" is running");
                System.out.println(threadName+" inner interrupt flag ="+isInterrupted());
            }
            System.out.println(threadName+" interrupt end flag ="+isInterrupted());
        }
    }
複製代碼

運行上面的程序:

開啓線程,休眠一微秒後調用 interrupt() 進行中斷

    public static void main(String[] args) throws InterruptedException {
        Thread endThread = new UseThread("test isInterrupted");
        endThread.start();
        Thread.sleep(1);
        endThread.interrupt();
    }
複製代碼

結果:

能夠看到 UseThreadisInterrupted() 一直爲 false ,當主線程執行 endThread.interrupt() 中斷方法後,其中斷標誌被置爲 true ,跳出循環,結束 run 方法。咱們後續再調用 isInterrupted() 方法打印中斷標誌的值一直爲 true,並無更改。

二、interrupted()

判斷線程是否中斷,若是該線程已被中斷返回 true,狀態返回後該方法會清除中斷標誌位,從新置爲 false,因此當第二次再次調用的時候獲取到的結果又會是 false ,(除非從新調用 interrupt()進行中斷 )

    /**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
複製代碼

interrupted() 示例:

咱們簡單改了一下代碼:

 private static class UseThread extends Thread{
        public UseThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();

            while(!Thread.interrupted()){
                System.out.println(threadName+" is running");
            }
            System.out.println(threadName+" interrupted end flag ="+Thread.interrupted());
        }
    }
複製代碼

能夠看到,run 方法裏面一直循環執行,直到線程被中斷,結束後咱們再次調用了打印了 Thread.interrupted()

調用:

    public static void main(String[] args) throws InterruptedException {
        Thread endThread = new UseThread("test interrupted");
        endThread.start();
        Thread.sleep(1);
        endThread.interrupt();
    }
複製代碼

一樣休眠一微秒後進行中斷操做。

結果:

咱們再分析一下,前面線程結束循環的條件是 Thread.interrupted()true , 可是當線程結束循環後,咱們再次調用 Thread.interrupted() 方法,發現其值爲又被置爲 false ,說明 Thread.interrupted() 執行後,會清除中斷標誌位,並將其從新置爲 false

注意:處於死鎖狀態的線程沒法被中斷

阻塞狀態下的線程中斷

若是一個線程處於了阻塞狀態(如線程調用了thread.sleep、thread.join、thread.wait),則在線程在檢查中斷標示時若是發現中斷標示爲true,則會在這些阻塞方法調用處拋出InterruptedException異常,而且在拋出異常後會當即將線程的中斷標示位清除,即從新設置爲false。

咱們在前面 isInterrupted 演示的示例中進行修改

 private static class UseThread extends Thread {
        public UseThread(String name{
            super(name);
        }

        @Override
        public void run(
{
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " interrupt start flag  =" + isInterrupted());
            while (!isInterrupted()) {
                try {
                    // 線程進行休眠3秒
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("sleep  error=" + e.getLocalizedMessage());
                }
                System.out.println(threadName + " is running");
                System.out.println(threadName + " inner interrupt flag =" + isInterrupted());
            }
            System.out.println(threadName + " interrupt end flag =" + isInterrupted());
        }
    }
複製代碼

咱們再執行前面的方法的時候,結果:

咱們能夠看到,即便拋了異常,可是線程依舊在執行,因此咱們要注意拋出 InterruptedException 異常的時候會 清除中斷標誌位 的操做,因此咱們改一下代碼,在catch 中再次執行 interrupt()來中斷任務

 try {
       // 線程進行休眠3秒
       Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
      // 在catch方法中,執行interrupt() 方法中斷任務。
      interrupt();
      System.out.println("sleep  error=" + e.getLocalizedMessage());
   }
複製代碼

結果:

線程在異常拋出以後,在catch中再次中斷,線程中止,再也不執行。

3、線程之間共享和協做

3.一、 線程之間共享

前面說過,同一個進程的全部線程共享該進程的所有資源,共享資源就會致使一個問題,當多個線程同時訪問一個對象或者一個對象的成員變量,可能會致使數據不一樣步問題,即線程不安全操做,好比 線程A 對 數據a 進行操做,須要從內存中進行讀取而後進行相應的操做,操做完成後再寫入內存中,可是若是數據尚未寫入內存中的時候,線程B 也來對這個數據進行操做,取到的就是還未寫入內存的數據,致使先後數據不一樣步。

爲了處理這個問題,Java 中引入了關鍵字 synchronized ( 下一篇文章單獨講)。

3.二、線程之間的協做

線程之間能夠相互配合,共同完成一項工做,好比線程A修改了某個值,這個時候須要通知另外一個線程再執行後續操做,整個過程開始與一個線程,最終又再另外一個線程執行,前者是生產者,後者就是消費者。

在 Android 中咱們在進行網絡請求的時候,每每會新開一個子線程來網絡請求獲取數據,獲取到數據後,又會經過Handle來通知主線程進行UI更新,這個操做就體現了線程之間的協做關係。

3.2.一、 nitify()、notifyAll()、wait() 等待/通知機制

是指一個線程A調用了對象Owait() 方法進入等待狀態,而另外一個線程B調用了對象Onotify()或者notifyAll()方法,線程A收到通知後從對象O的wait()方法返回,進而執行後續操做。上述兩個線程經過對象O來完成交互,而對象上的wait()notify、notifyAll()的關係就如同開關信號同樣,用來完成等待方和通知方之間的交互工做。

咱們知道 Object 類是全部類的父類,而 Object 類中就存在相關方法

notify():

通知一個在對象上等待的線程,使其從wait方法返回,而返回的前提是該線程獲取到了對象的鎖,沒有得到鎖的線程從新進入WAITING狀態。

notifyAll():

通知全部等待在該對象上的線程

wait()

調用該方法的線程進入 WAITING狀態,只有等待另外線程的通知或被中斷纔會返回.須要注意,調用wait()方法後,會釋放對象的鎖

wait(long)

超時等待一段時間,這裏的參數時間是毫秒,也就是等待長達n毫秒,若是沒有通知就超時返回

wait (long,int)

對於超時時間更細粒度的控制,能夠達到納秒

下面經過案例說明,雙十一的時候,你購買了三件商品,你在家焦急的等待,沒事就刷新一下手機看商品快遞信息,咱們就來模擬一個快遞信息的更新,這裏以地點變化進行數據更新:

public class NwTest {
    // 發貨地點
    public String location = "重慶";
    // 全部貨物在不一樣一趟車上,貨物到了下一站,分別更新對應的快遞信息
    public synchronized void changeLocationNotify(String location) {
        this.location = location;
        this.notify();
    }
    // 全部貨物在同一趟快遞車上,貨物到了下一站,所有信息更新。
    public synchronized void changeLocationNotifyAll(String location) {
        this.location = location;
        System.out.println("changeLocationNotifyAll");
        this.notifyAll();
    }

    public static class LocationThread extends Thread {
        public final NwTest mNwTest;
        public LocationThread(NwTest nwTest) {
            this.mNwTest = nwTest;
        }

        @Override
        public void run() {
            super.run();
            try {
                synchronized (mNwTest) {
                    System.out.println("LocationThread  current location : " + mNwTest.location);    
                    // 等待位置更新
                    mNwTest.wait();
                    String name = Thread.currentThread().getName();
                    // 獲取當前商品的商家信息
                    System.out.println("LocationThread——>current thread name : " + name);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 獲取更新後的位置
            System.out.println("LocationThread  update location : " + mNwTest.location);
        }
    }
複製代碼

注意:

只能在同步方法或者同步塊中使用 wait()notifyAll()notify() 方法

不然會拋 IllegalMonitorStateException 異常

調用:

    public static void main(String[] args) throws InterruptedException {
        NwTest nwTest = new NwTest();
        for (int x = 0; x < 3; x++) {
            new LocationThread(nwTest).start();
        }
        // 模擬三天後
        Thread.sleep(3000);
        // 通知單個商品信息進行更新
        nwTest.changeLocationNotify("合肥");
    }
複製代碼

咱們啓動了三個線程,模擬了你購買的三件貨物,若是使用 notify() ,只是使單個商品進行信息更新(隨機喚醒)

結果:

咱們看到三個貨物同時發貨,其中 Thread_0 最早到達合肥,並進行了數據更新。

若是使用 notifyAll() ,全部商品快遞信息都會刷新。

    public static void main(String[] args) throws InterruptedException {
        NwTest nwTest = new NwTest();
        for (int x = 0; x < 3; x++) {
            new LocationThread(nwTest).start();
        }
        Thread.sleep(3000);
        // 通知三件商品進行信息更新
        nwTest.changeLocationNotifyAll("合肥");
    }
複製代碼

結果:

這就是 notifyAll()notify()wait() 基本使用,其中 wait(long) 表示線程會等待 n 毫秒,若是這個時間段內沒有收到 notifyAll() 或者 notify() 就自動執行後續方法。

根據上面的Demo,咱們能夠整理一下 等待和通知的標準範式

wait():

1)獲取對象的鎖。

2)根據判斷條件調用 wait() 方法。

3)條件知足則執行對應的邏輯。

notify() 或者 notifyAll()

1)得到對象的鎖。

2)改變條件,發送通知。

3)通知全部等待在對象上的線程。

以上主要是整理的多線程的一些基本概念,還有 notify()和wait() 的基本使用,關鍵字 synchronized 準備下一篇單獨整理,後續計劃整理線程池相關知識以及Android 中 AsyncTask 的源碼分析,喜歡的話點個讚唄!

相關文章
相關標籤/搜索