Java高併發編程(一)

快速認識線程

在計算機的世界中,當我們在討論並行的時候,實際上是指,一系列的任務在計算機中同時運行,比如說我們在瀏覽網頁的時候還能打開音樂播放器,當我們在撰寫郵件的時候,收件箱還能接收新的郵件。在單CPU的計算機中,其實並沒有真正的並行,它只不過是CPU時間鐘快速輪轉帶給你的錯覺,而這種錯覺讓你產生了它們是在同一時刻同時運行的。當然,如果是多核CPU,那麼並行運行還是真實存在的。

什麼是線程

這是從百度上面摘抄下來的線程的概念:

  • 線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。在Unix System V及SunOS中也被稱爲輕量進程(lightweight processes),但輕量進程更多指內核線程(kernel thread),而把用戶線程(user thread)稱爲線程。
    線程是獨立調度和分派的基本單位。線程可以爲操作系統內核調度的內核線程,如Win32線程;由用戶進程自行調度的用戶線程,如Linux平臺的POSIX Thread;或者由內核與用戶進程,如Windows 7的線程,進行混合調度。
  • 同一進程中的多條線程將共享該進程中的全部系統資源,如虛擬地址空間,文件描述符和信號處理等等。但同一進程中的多個線程有各自的調用棧(call stack),自己的寄存器環境(register context),自己的線程本地存儲(thread-local storage)。
    一個進程可以有很多線程,每條線程並行執行不同的任務。
  • 在多核或多CPU,或支持Hyper-threading的CPU上使用多線程程序設計的好處是顯而易見,即提高了程序的執行吞吐率。在單CPU單核的計算機上,使用多線程技術,也可以把進程中負責I/O處理、人機交互而常被阻塞的部分與密集計算的部分分開來執行,編寫專門的workhorse線程執行密集計算,從而提高了程序的執行效率。

  • 現在的操作系統都是支持多任務的執行,對於計算機來說每一個任務就是一個進程,在每一個進程內部至少要有一個線程是在運行的,有的時候,線程也被稱爲輕量級的進程。
  • 線程是程序執行的一個路徑,每一個線程都有自己的局部變量表、程序計數器(指向正在執行的指令指針)以及各自的生命週期,現代的操作系統中一般不止一個線程在運行,當啓動了一個Java虛擬機(JVM)時,從操作系統開始就會創建一個新的進程(JVM進程),JVM進程中將會派生或者創建很多的線程。

創建和啓動線程

代碼如下:此時是先後執行,不能同時的去做處理

public class TryConcurrency {

    public static void main(String[] args) {
        readFromDatabase();
        writeDataToFile();
    }

    private static void readFromDatabase() {
        //read data from database and handle it
        try {
            println("Begin read data from db.");
            Thread.sleep(1000*10L);
            println("Read data done and start handle it.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        println("The data handle finish and successfully.");
    }

    private static void writeDataToFile() {
        //read data from database and handle it
        try {
            println("Begin write data to file.");
            Thread.sleep(1000*10L);
            println("Write data done and start handle it.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        println("The data handle finish and successfully.");
    }

    private static void println(String message) {
        System.out.println(message);
    }
}

運行結果爲:兩個方法是先後執行的,並不是同時運行的

Begin read data from db.
Read data done and start handle it.
The data handle finish and successfully.
Begin write data to file.
Write data done and start handle it.
The data handle finish and successfully.

而此時,如果我們想要它們能同時運行的話,我們可以這樣來寫:

public class TryConcurrency {

    public static void main(String[] args) {
        new Thread(TryConcurrency::readFromDatabase).start();
        writeDataToFile();
    }

    private static void readFromDatabase() {
        //read data from database and handle it
        try {
            println("Begin read data from db.");
            Thread.sleep(1000*10L);
            println("Read data done and start handle it.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        println("The data handle finish and successfully.");
    }

    private static void writeDataToFile() {
        //read data from database and handle it
        try {
            println("Begin write data to file.");
            Thread.sleep(1000*10L);
            println("Write data done and start handle it.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        println("The data handle finish and successfully.");
    }

    private static void println(String message) {
        System.out.println(message);
    }
}

此時的運行結果爲:此時兩個方法就是並行的

Begin write data to file.
Begin read data from db.
Write data done and start handle it.
Read data done and start handle it.
The data handle finish and successfully.
The data handle finish and successfully.


線程的生命週期詳解

下圖爲線程的生命週期:
線程的生命週期
線程的生命週期大體可以分爲5個主要階段:

  1. 線程的new狀態:
    當我們用關鍵字new創建一個Thread對象時,此時它並不處於執行狀態,因爲還沒有調用start方法啓動該線程,在還沒有調用start方法之前,該線程根本不存在,和用new關鍵字創建普通對象沒有什麼區別。當調用了start方法後則進入Runnable狀態。
  2. 線程的Runnable狀態(準備狀態)
    線程對象在進入到Runnable狀態就必須要調用start方法,那麼此時纔是真正的在JVM進程中創建了一個線程,那麼線程在一經啓動就可以立即執行嗎?答案是否定的,線程的是否執行和進程一樣得聽令於CPU的調度,那麼我們把這個中間狀態稱爲可執行狀態(Runnable),也就是說它具備可執行的資格,但是並沒有真正的被執行起來,而是在等待CPU的調度。
  3. 線程的Running狀態(運行狀態)
    一旦當進入到Runnable狀態的線程得到了CPU的調度,那麼該線程則進入到了Running狀態。在Running狀態下可以發生如下的狀態轉換:
    • 直接進入Terminated狀態,比如說調用了jdk已經不推薦使用的stop方法或者判斷某個邏輯標識
    • 進入Blocked(阻塞)狀態,比如說調用了sleep或者wait方法,從而進入到waitSet(等待隊列)中
    • 進行某一個阻塞的IO操作,比如說因爲網絡數據的讀寫而進入到了阻塞狀態
    • 獲取某一個鎖資源,從而進入到阻塞隊列中,進入阻塞狀態
    • 由於CPU的調度器輪詢使該線程放棄執行,進入到Runnable(準備狀態)
    • 線程主動調用yield方法,放棄CPU執行權,進入到Runnable(準備狀態)
  4. 線程的Blocked狀態(阻塞狀態)
    進入到阻塞狀態,可以進行如下的狀態轉換
    • 直接進入Terminated狀態,比如說調用了jdk已經不推薦使用的stop方法或者意外死亡(JVM Crash)
    • 線程阻塞的操作結束,進入到Runnable狀態
    • 線程完成了指定的休眠時間,進入到Runnable狀態
    • wait中的線程被其他線程notify/notifyall喚醒,進入到Runnable狀態
    • 線程獲取到某一個鎖資源,進入到Runnable狀態
    • 線程在阻塞過程中被打斷,比如其他線程調用了interrupt方法,進入到Runnable狀態
  5. 線程的Teminated狀態
    Teminated是線程的一個最終狀態,在該狀態中的線程不會切換到其他的任何狀態,線程進入到Teminated狀態,則意味着該線程的整個生命週期結束了,下面幾種情況會進入到該狀態:
    • 線程運行正常結束,結束整個生命週期
    • 線程運行出錯意外結束
    • JVM Crash,導致所有的線程都結束

線程的start方法剖析

Thread類中的start方法源碼如下:

    /**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */
    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

從源碼,我們可以看出一個線程不可以調用start()方法兩次,否則則會拋出IllegalThreadStateException異常;
實際上,Thread類中的start方法和run方法是一個模板方法設計模式的實現;


總結:

  • Java應用程序中的main函數是一個線程,就被JVM啓動的時候調用,線程的名字叫main
  • 實現一個線程,必須要創建Thread類的實例,重寫run方法,並且調用start方法
  • 在JVM啓動後,實際上有多個線程,但是至少有一個非守護線程
  • 當你調用一個線程start方法的時候,此時至少有兩個線程,一個是調用你的線程,還有一個是執行run方法的線程
  • 線程的生命週期分爲new、runnable、running、block、terminate

Thread實戰案例

用Thread模擬在銀行排隊辦理業務的場景:假設有3個窗口,我們用3個線程去模擬叫號的過程;


現在是一個櫃檯的情況:

public class TicketWindow extends Thread{
    /**
     * 櫃檯的名字
     */
    private final String name;

    /**
     * 最大的叫號數
     */
    private final int MAX = 50;

    /**
     * 被叫的號碼數
     */
    private int index = 1;

    public TicketWindow(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        while (index <= MAX) {
            System.out.println("櫃檯:" + name + ",當前的號碼是:" + index++);
        }
    }
}

我們啓動線程:

public class Blank {
    public static void main(String[] args) {
        TicketWindow ticketWindow = new TicketWindow("一號櫃檯");
        ticketWindow.start();
    }
}

運行的結果如下:

櫃檯:一號櫃檯,當前的號碼是:1
櫃檯:一號櫃檯,當前的號碼是:2
櫃檯:一號櫃檯,當前的號碼是:3
櫃檯:一號櫃檯,當前的號碼是:4
櫃檯:一號櫃檯,當前的號碼是:5
櫃檯:一號櫃檯,當前的號碼是:6
櫃檯:一號櫃檯,當前的號碼是:7
櫃檯:一號櫃檯,當前的號碼是:8
櫃檯:一號櫃檯,當前的號碼是:9
櫃檯:一號櫃檯,當前的號碼是:10
櫃檯:一號櫃檯,當前的號碼是:11
櫃檯:一號櫃檯,當前的號碼是:12
櫃檯:一號櫃檯,當前的號碼是:13
櫃檯:一號櫃檯,當前的號碼是:14
櫃檯:一號櫃檯,當前的號碼是:15
櫃檯:一號櫃檯,當前的號碼是:16
櫃檯:一號櫃檯,當前的號碼是:17
櫃檯:一號櫃檯,當前的號碼是:18
櫃檯:一號櫃檯,當前的號碼是:19
櫃檯:一號櫃檯,當前的號碼是:20
櫃檯:一號櫃檯,當前的號碼是:21
櫃檯:一號櫃檯,當前的號碼是:22
櫃檯:一號櫃檯,當前的號碼是:23
櫃檯:一號櫃檯,當前的號碼是:24
櫃檯:一號櫃檯,當前的號碼是:25
櫃檯:一號櫃檯,當前的號碼是:26
櫃檯:一號櫃檯,當前的號碼是:27
櫃檯:一號櫃檯,當前的號碼是:28
櫃檯:一號櫃檯,當前的號碼是:29
櫃檯:一號櫃檯,當前的號碼是:30
櫃檯:一號櫃檯,當前的號碼是:31
櫃檯:一號櫃檯,當前的號碼是:32
櫃檯:一號櫃檯,當前的號碼是:33
櫃檯:一號櫃檯,當前的號碼是:34
櫃檯:一號櫃檯,當前的號碼是:35
櫃檯:一號櫃檯,當前的號碼是:36
櫃檯:一號櫃檯,當前的號碼是:37
櫃檯:一號櫃檯,當前的號碼是:38
櫃檯:一號櫃檯,當前的號碼是:39
櫃檯:一號櫃檯,當前的號碼是:40
櫃檯:一號櫃檯,當前的號碼是:41
櫃檯:一號櫃檯,當前的號碼是:42
櫃檯:一號櫃檯,當前的號碼是:43
櫃檯:一號櫃檯,當前的號碼是:44
櫃檯:一號櫃檯,當前的號碼是:45
櫃檯:一號櫃檯,當前的號碼是:46
櫃檯:一號櫃檯,當前的號碼是:47
櫃檯:一號櫃檯,當前的號碼是:48
櫃檯:一號櫃檯,當前的號碼是:49
櫃檯:一號櫃檯,當前的號碼是:50


現在,我們來模擬3個櫃檯的叫號過程:

public class TicketWindow extends Thread{
    /**
     * 櫃檯的名字
     */
    private final String name;

    /**
     * 最大的叫號數
     */
    private final int MAX = 50;

    /**
     * 被叫的號碼數
     */
    private int index = 1;

    public TicketWindow(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        while (index <= MAX) {
            System.out.println("櫃檯:" + name + ",當前的號碼是:" + index++);
        }
    }
}

現在,我們啓動3個線程:

public class Blank {
    public static void main(String[] args) {
        TicketWindow ticketWindow1 = new TicketWindow("一號櫃檯");
        ticketWindow1.start();

        TicketWindow ticketWindow2 = new TicketWindow("二號櫃檯");
        ticketWindow2.start();

        TicketWindow ticketWindow3 = new TicketWindow("三號櫃檯");
        ticketWindow3.start();
    }
}

執行的結果如下:

櫃檯:一號櫃檯,當前的號碼是:1
櫃檯:一號櫃檯,當前的號碼是:2
櫃檯:一號櫃檯,當前的號碼是:3
櫃檯:一號櫃檯,當前的號碼是:4
櫃檯:一號櫃檯,當前的號碼是:5
櫃檯:二號櫃檯,當前的號碼是:1
櫃檯:二號櫃檯,當前的號碼是:2
櫃檯:一號櫃檯,當前的號碼是:6
櫃檯:二號櫃檯,當前的號碼是:3
櫃檯:三號櫃檯,當前的號碼是:1
櫃檯:二號櫃檯,當前的號碼是:4
櫃檯:一號櫃檯,當前的號碼是:7
櫃檯:二號櫃檯,當前的號碼是:5
櫃檯:二號櫃檯,當前的號碼是:6
櫃檯:二號櫃檯,當前的號碼是:7
櫃檯:二號櫃檯,當前的號碼是:8
櫃檯:二號櫃檯,當前的號碼是:9
櫃檯:二號櫃檯,當前的號碼是:10
櫃檯:二號櫃檯,當前的號碼是:11
櫃檯:二號櫃檯,當前的號碼是:12
櫃檯:二號櫃檯,當前的號碼是:13
櫃檯:二號櫃檯,當前的號碼是:14
櫃檯:二號櫃檯,當前的號碼是:15
櫃檯:二號櫃檯,當前的號碼是:16
櫃檯:二號櫃檯,當前的號碼是:17
櫃檯:二號櫃檯,當前的號碼是:18
櫃檯:二號櫃檯,當前的號碼是:19
櫃檯:二號櫃檯,當前的號碼是:20
櫃檯:二號櫃檯,當前的號碼是:21
櫃檯:二號櫃檯,當前的號碼是:22
櫃檯:二號櫃檯,當前的號碼是:23
櫃檯:二號櫃檯,當前的號碼是:24
櫃檯:二號櫃檯,當前的號碼是:25
櫃檯:二號櫃檯,當前的號碼是:26
櫃檯:二號櫃檯,當前的號碼是:27
櫃檯:二號櫃檯,當前的號碼是:28
櫃檯:二號櫃檯,當前的號碼是:29
櫃檯:二號櫃檯,當前的號碼是:30
櫃檯:二號櫃檯,當前的號碼是:31
櫃檯:二號櫃檯,當前的號碼是:32
櫃檯:二號櫃檯,當前的號碼是:33
櫃檯:二號櫃檯,當前的號碼是:34
櫃檯:二號櫃檯,當前的號碼是:35
櫃檯:三號櫃檯,當前的號碼是:2
櫃檯:三號櫃檯,當前的號碼是:3
櫃檯:三號櫃檯,當前的號碼是:4
櫃檯:二號櫃檯,當前的號碼是:36
櫃檯:二號櫃檯,當前的號碼是:37
櫃檯:二號櫃檯,當前的號碼是:38
櫃檯:二號櫃檯,當前的號碼是:39
櫃檯:二號櫃檯,當前的號碼是:40
櫃檯:二號櫃檯,當前的號碼是:41
櫃檯:二號櫃檯,當前的號碼是:42
櫃檯:二號櫃檯,當前的號碼是:43
櫃檯:二號櫃檯,當前的號碼是:44
櫃檯:二號櫃檯,當前的號碼是:45
櫃檯:二號櫃檯,當前的號碼是:46
櫃檯:二號櫃檯,當前的號碼是:47
櫃檯:二號櫃檯,當前的號碼是:48
櫃檯:二號櫃檯,當前的號碼是:49
櫃檯:二號櫃檯,當前的號碼是:50
櫃檯:一號櫃檯,當前的號碼是:8
櫃檯:一號櫃檯,當前的號碼是:9
櫃檯:一號櫃檯,當前的號碼是:10
櫃檯:一號櫃檯,當前的號碼是:11
櫃檯:一號櫃檯,當前的號碼是:12
櫃檯:一號櫃檯,當前的號碼是:13
櫃檯:一號櫃檯,當前的號碼是:14
櫃檯:一號櫃檯,當前的號碼是:15
櫃檯:一號櫃檯,當前的號碼是:16
櫃檯:一號櫃檯,當前的號碼是:17
櫃檯:一號櫃檯,當前的號碼是:18
櫃檯:一號櫃檯,當前的號碼是:19
櫃檯:一號櫃檯,當前的號碼是:20
櫃檯:一號櫃檯,當前的號碼是:21
櫃檯:一號櫃檯,當前的號碼是:22
櫃檯:一號櫃檯,當前的號碼是:23
櫃檯:一號櫃檯,當前的號碼是:24
櫃檯:一號櫃檯,當前的號碼是:25
櫃檯:一號櫃檯,當前的號碼是:26
櫃檯:一號櫃檯,當前的號碼是:27
櫃檯:一號櫃檯,當前的號碼是:28
櫃檯:一號櫃檯,當前的號碼是:29
櫃檯:一號櫃檯,當前的號碼是:30
櫃檯:一號櫃檯,當前的號碼是:31
櫃檯:一號櫃檯,當前的號碼是:32
櫃檯:一號櫃檯,當前的號碼是:33
櫃檯:一號櫃檯,當前的號碼是:34
櫃檯:一號櫃檯,當前的號碼是:35
櫃檯:一號櫃檯,當前的號碼是:36
櫃檯:一號櫃檯,當前的號碼是:37
櫃檯:一號櫃檯,當前的號碼是:38
櫃檯:一號櫃檯,當前的號碼是:39
櫃檯:一號櫃檯,當前的號碼是:40
櫃檯:一號櫃檯,當前的號碼是:41
櫃檯:一號櫃檯,當前的號碼是:42
櫃檯:一號櫃檯,當前的號碼是:43
櫃檯:一號櫃檯,當前的號碼是:44
櫃檯:一號櫃檯,當前的號碼是:45
櫃檯:一號櫃檯,當前的號碼是:46
櫃檯:一號櫃檯,當前的號碼是:47
櫃檯:一號櫃檯,當前的號碼是:48
櫃檯:一號櫃檯,當前的號碼是:49
櫃檯:一號櫃檯,當前的號碼是:50
櫃檯:三號櫃檯,當前的號碼是:5
櫃檯:三號櫃檯,當前的號碼是:6
櫃檯:三號櫃檯,當前的號碼是:7
櫃檯:三號櫃檯,當前的號碼是:8
櫃檯:三號櫃檯,當前的號碼是:9
櫃檯:三號櫃檯,當前的號碼是:10
櫃檯:三號櫃檯,當前的號碼是:11
櫃檯:三號櫃檯,當前的號碼是:12
櫃檯:三號櫃檯,當前的號碼是:13
櫃檯:三號櫃檯,當前的號碼是:14
櫃檯:三號櫃檯,當前的號碼是:15
櫃檯:三號櫃檯,當前的號碼是:16
櫃檯:三號櫃檯,當前的號碼是:17
櫃檯:三號櫃檯,當前的號碼是:18
櫃檯:三號櫃檯,當前的號碼是:19
櫃檯:三號櫃檯,當前的號碼是:20
櫃檯:三號櫃檯,當前的號碼是:21
櫃檯:三號櫃檯,當前的號碼是:22
櫃檯:三號櫃檯,當前的號碼是:23
櫃檯:三號櫃檯,當前的號碼是:24
櫃檯:三號櫃檯,當前的號碼是:25
櫃檯:三號櫃檯,當前的號碼是:26
櫃檯:三號櫃檯,當前的號碼是:27
櫃檯:三號櫃檯,當前的號碼是:28
櫃檯:三號櫃檯,當前的號碼是:29
櫃檯:三號櫃檯,當前的號碼是:30
櫃檯:三號櫃檯,當前的號碼是:31
櫃檯:三號櫃檯,當前的號碼是:32
櫃檯:三號櫃檯,當前的號碼是:33
櫃檯:三號櫃檯,當前的號碼是:34
櫃檯:三號櫃檯,當前的號碼是:35
櫃檯:三號櫃檯,當前的號碼是:36
櫃檯:三號櫃檯,當前的號碼是:37
櫃檯:三號櫃檯,當前的號碼是:38
櫃檯:三號櫃檯,當前的號碼是:39
櫃檯:三號櫃檯,當前的號碼是:40
櫃檯:三號櫃檯,當前的號碼是:41
櫃檯:三號櫃檯,當前的號碼是:42
櫃檯:三號櫃檯,當前的號碼是:43
櫃檯:三號櫃檯,當前的號碼是:44
櫃檯:三號櫃檯,當前的號碼是:45
櫃檯:三號櫃檯,當前的號碼是:46
櫃檯:三號櫃檯,當前的號碼是:47
櫃檯:三號櫃檯,當前的號碼是:48
櫃檯:三號櫃檯,當前的號碼是:49
櫃檯:三號櫃檯,當前的號碼是:50

上面的這種方式顯然是不合理的,每一個線程都從1叫到了50


我們再來進行改造一下:爲共享變量添加static關鍵字

public class TicketWindow extends Thread{
    /**
     * 櫃檯的名字
     */
    private final String name;

    /**
     * 最大的叫號數
     */
    private static final int MAX = 50;

    /**
     * 被叫的號碼數
     */
    private static int index = 1;

    public TicketWindow(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        while (index <= MAX) {
            System.out.println("櫃檯:" + name + ",當前的號碼是:" + index++);
        }
    }
}

我們再來運行這3個線程看看運行結果:

public class Blank {
    public static void main(String[] args) {
        TicketWindow ticketWindow1 = new TicketWindow("一號櫃檯");
        ticketWindow1.start();

        TicketWindow ticketWindow2 = new TicketWindow("二號櫃檯");
        ticketWindow2.start();

        TicketWindow ticketWindow3 = new TicketWindow("三號櫃檯");
        ticketWindow3.start();
    }
}

此時的運行結果爲:

櫃檯:一號櫃檯,當前的號碼是:1
櫃檯:二號櫃檯,當前的號碼是:2
櫃檯:二號櫃檯,當前的號碼是:4
櫃檯:一號櫃檯,當前的號碼是:3
櫃檯:一號櫃檯,當前的號碼是:6
櫃檯:一號櫃檯,當前的號碼是:7
櫃檯:二號櫃檯,當前的號碼是:5
櫃檯:二號櫃檯,當前的號碼是:10
櫃檯:二號櫃檯,當前的號碼是:11
櫃檯:一號櫃檯,當前的號碼是:9
櫃檯:三號櫃檯,當前的號碼是:8
櫃檯:一號櫃檯,當前的號碼是:13
櫃檯:二號櫃檯,當前的號碼是:12
櫃檯:二號櫃檯,當前的號碼是:16
櫃檯:二號櫃檯,當前的號碼是:17
櫃檯:二號櫃檯,當前的號碼是:18
櫃檯:二號櫃檯,當前的號碼是:19
櫃檯:二號櫃檯,當前的號碼是:20
櫃檯:二號櫃檯,當前的號碼是:21
櫃檯:二號櫃檯,當前的號碼是:22
櫃檯:二號櫃檯,當前的號碼是:23
櫃檯:二號櫃檯,當前的號碼是:24
櫃檯:二號櫃檯,當前的號碼是:25
櫃檯:二號櫃檯,當前的號碼是:26
櫃檯:二號櫃檯,當前的號碼是:27
櫃檯:二號櫃檯,當前的號碼是:28
櫃檯:二號櫃檯,當前的號碼是:29
櫃檯:二號櫃檯,當前的號碼是:30
櫃檯:二號櫃檯,當前的號碼是:31
櫃檯:二號櫃檯,當前的號碼是:32
櫃檯:二號櫃檯,當前的號碼是:33
櫃檯:二號櫃檯,當前的號碼是:34
櫃檯:二號櫃檯,當前的號碼是:35
櫃檯:二號櫃檯,當前的號碼是:36
櫃檯:二號櫃檯,當前的號碼是:37
櫃檯:二號櫃檯,當前的號碼是:38
櫃檯:二號櫃檯,當前的號碼是:39
櫃檯:二號櫃檯,當前的號碼是:40
櫃檯:二號櫃檯,當前的號碼是:41
櫃檯:二號櫃檯,當前的號碼是:42
櫃檯:二號櫃檯,當前的號碼是:43
櫃檯:二號櫃檯,當前的號碼是:44
櫃檯:二號櫃檯,當前的號碼是:45
櫃檯:二號櫃檯,當前的號碼是:46
櫃檯:二號櫃檯,當前的號碼是:47
櫃檯:二號櫃檯,當前的號碼是:48
櫃檯:二號櫃檯,當前的號碼是:49
櫃檯:二號櫃檯,當前的號碼是:50
櫃檯:一號櫃檯,當前的號碼是:15
櫃檯:三號櫃檯,當前的號碼是:14


接下來,我們用Runnable接口將線程中的執行邏輯執行單元從控制中抽取出來:讓多個線程共用這個Runnable的實例

public class TicketWindowRunnable implements Runnable {

    /**
     * 最大的叫號數
     */
    private final int MAX = 50;

    /**
     * 被叫的號碼數
     */
    private int index = 1;

    @Override
    public void run() {
        while (index <= MAX) {
            System.out.println(Thread.currentThread().getName() + "的號碼是:" + index++);
        }
    }
}

調用start方法來啓動線程:

public class Blank2 {
    public static void main(String[] args) {
        final TicketWindowRunnable ticketWindowRunnable = new TicketWindowRunnable();
        Thread windowThread1 = new Thread(ticketWindowRunnable, "一號櫃檯");
        Thread windowThread2 = new Thread(ticketWindowRunnable, "二號櫃檯");
        Thread windowThread3 = new Thread(ticketWindowRunnable, "三號櫃檯");
        windowThread1.start();
        windowThread2.start();
        windowThread3.start();
    }
}

執行結果如下:雖然只有50號碼,但是其實在下面的執行結果是有線程安全問題的,號碼爲1的出現過了兩次

一號櫃檯的號碼是:1
三號櫃檯的號碼是:2
二號櫃檯的號碼是:1
三號櫃檯的號碼是:4
三號櫃檯的號碼是:6
一號櫃檯的號碼是:3
三號櫃檯的號碼是:7
二號櫃檯的號碼是:5
三號櫃檯的號碼是:9
一號櫃檯的號碼是:8
三號櫃檯的號碼是:11
二號櫃檯的號碼是:10
三號櫃檯的號碼是:13
一號櫃檯的號碼是:12
三號櫃檯的號碼是:15
二號櫃檯的號碼是:14
三號櫃檯的號碼是:17
一號櫃檯的號碼是:16
三號櫃檯的號碼是:19
二號櫃檯的號碼是:18
三號櫃檯的號碼是:21
一號櫃檯的號碼是:20
三號櫃檯的號碼是:23
二號櫃檯的號碼是:22
三號櫃檯的號碼是:25
一號櫃檯的號碼是:24
三號櫃檯的號碼是:27
二號櫃檯的號碼是:26
三號櫃檯的號碼是:29
二號櫃檯的號碼是:30
一號櫃檯的號碼是:28
二號櫃檯的號碼是:32
三號櫃檯的號碼是:31
三號櫃檯的號碼是:35
二號櫃檯的號碼是:34
一號櫃檯的號碼是:33
一號櫃檯的號碼是:38
一號櫃檯的號碼是:39
一號櫃檯的號碼是:40
二號櫃檯的號碼是:37
三號櫃檯的號碼是:36
二號櫃檯的號碼是:42
一號櫃檯的號碼是:41
二號櫃檯的號碼是:44
三號櫃檯的號碼是:43
二號櫃檯的號碼是:46
一號櫃檯的號碼是:45
二號櫃檯的號碼是:48
三號櫃檯的號碼是:47
二號櫃檯的號碼是:50
一號櫃檯的號碼是:49


策略模式在Runnable和Thread中的應用
策略模式


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章