Java執行緒小刀牛試

李紅歐巴發表於2019-04-01

執行緒簡介

什麼是執行緒

現代作業系統排程的最小單元是執行緒,也叫輕量級程式(Light Weight Process),在一個程式裡可以建立多個執行緒,這些執行緒都擁有各自的計數器、堆疊和區域性變數等屬性,並且能夠訪問共享的記憶體變數。

執行緒生命週期

Java執行緒小刀牛試

java.lang.Thread.State 中定義了 6 種不同的執行緒狀態,在給定的一個時刻,執行緒只能處於其中的一個狀態。

以下是各狀態的說明,以及狀態間的聯絡:

  • 開始(New) - 還沒有呼叫 start() 方法的執行緒處於此狀態。
  • 可執行(Runnable) - 已經呼叫了 start() 方法的執行緒狀態。此狀態意味著,執行緒已經準備好了,一旦被執行緒排程器分配了 CPU 時間片,就可以執行執行緒。
  • 阻塞(Blocked) - 阻塞狀態。執行緒阻塞的執行緒狀態等待監視器鎖定。處於阻塞狀態的執行緒正在等待監視器鎖定,以便在呼叫 Object.wait() 之後輸入同步塊/方法或重新輸入同步塊/方法。
  • 等待(Waiting) - 等待狀態。一個執行緒處於等待狀態,是由於執行了 3 個方法中的任意方法:
    • Object.wait()
    • Thread.join()
    • LockSupport.park()
  • 定時等待(Timed waiting) - 等待指定時間的狀態。一個執行緒處於定時等待狀態,是由於執行了以下方法中的任意方法:
    • Thread.sleep(sleeptime)
    • Object.wait(timeout)
    • Thread.join(timeout)
    • LockSupport.parkNanos(timeout)
    • LockSupport.parkUntil(timeout)
  • 終止(Terminated) - 執行緒 run() 方法執行結束,或者因異常退出了 run() 方法,則該執行緒結束生命週期。死亡的執行緒不可再次復生。

啟動和終止執行緒

構造執行緒

構造執行緒主要有三種方式

  • 繼承 Thread
  • 實現 Runnable 介面
  • 實現 Callable 介面

繼承 Thread 類

通過繼承 Thread 類構造執行緒的步驟:

  • 定義 Thread 類的子類,並重寫該類的 run() 方法,該 run() 方法的方法體就代表了執行緒要完成的任務。因此把 run() 方法稱為執行體。
  • 建立 Thread 子類的例項,即建立了執行緒物件。
  • 呼叫執行緒物件的 start() 方法來啟動該執行緒。

示例:

public class ThreadDemo02 {

    public static void main(String[] args) {
        Thread02 mt1 = new Thread02("執行緒A "); // 例項化物件
        Thread02 mt2 = new Thread02("執行緒B "); // 例項化物件
        mt1.start(); // 呼叫執行緒主體
        mt2.start(); // 呼叫執行緒主體
    }

    static class Thread02 extends Thread {

        private int ticket = 5;

        Thread02(String name) {
            super(name);
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (this.ticket > 0) {
                    System.out.println(this.getName() + " 賣票:ticket = " + ticket--);
                }
            }
        }
    }
}複製程式碼

實現 Runnable 介面

通過實現 Runnable 介面構造執行緒的步驟:

  • 定義 Runnable 介面的實現類,並重寫該介面的 run() 方法,該 run() 方法的方法體同樣是該執行緒的執行緒執行體。
  • 建立 Runnable 實現類的例項,並依此例項作為 Thread 的 target 來建立 Thread 物件,該 Thread 物件才是真正的執行緒物件。
  • 呼叫執行緒物件的 start() 方法來啟動該執行緒。

示例:

public class RunnableDemo {

    public static void main(String[] args) {
        MyThread t = new MyThread("Runnable 執行緒"); // 例項化物件
        new Thread(t).run(); // 呼叫執行緒主體
        new Thread(t).run(); // 呼叫執行緒主體
        new Thread(t).run(); // 呼叫執行緒主體
    }

    static class MyThread implements Runnable {

        private int ticket = 5;
        private String name;

        MyThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (this.ticket > 0) {
                    System.out.println(this.name + " 賣票:ticket = " + ticket--);
                }
            }
        }
    }
}複製程式碼

實現 Callable 介面

通過實現 Callable 介面構造執行緒的步驟:

  • 建立 Callable 介面的實現類,並實現 call() 方法,該 call() 方法將作為執行緒執行體,並且有返回值。
  • 建立 Callable 實現類的例項,使用 FutureTask 類來包裝 Callable 物件,該 FutureTask 物件封裝了該 Callable 物件的 call() 方法的返回值。
  • 使用 FutureTask 物件作為 Thread 物件的 target 建立並啟動新執行緒。
  • 呼叫 FutureTask 物件的 get() 方法來獲得子執行緒執行結束後的返回值。

示例:

public class CallableAndFutureDemo {

    public static void main(String[] args) {
        Callable<Integer> callable = () -> new Random().nextInt(100);
        FutureTask<Integer> future = new FutureTask<>(callable);
        new Thread(future).start();
        try {
            Thread.sleep(1000);// 可能做一些事情
            System.out.println(future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}複製程式碼

三種建立執行緒方式對比

  • 實現 Runnable 介面優於繼承 Thread 類,因為實現介面方式更便於擴充套件類。
  • 實現 Runnable 介面的執行緒沒有返回值;而實現 Callable 介面的執行緒有返回值

中斷執行緒

當一個執行緒執行時,另一個執行緒可以直接通過 interrupt() 方法中斷其執行狀態。

public class ThreadInterruptDemo {

    public static void main(String[] args) {
        MyThread mt = new MyThread(); // 例項化Runnable子類物件
        Thread t = new Thread(mt, "執行緒"); // 例項化Thread物件
        t.start(); // 啟動執行緒
        try {
            Thread.sleep(2000); // 執行緒休眠2秒
        } catch (InterruptedException e) {
            System.out.println("3、休眠被終止");
        }
        t.interrupt(); // 中斷執行緒執行
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            System.out.println("1、進入run()方法");
            try {
                Thread.sleep(10000); // 執行緒休眠10秒
                System.out.println("2、已經完成了休眠");
            } catch (InterruptedException e) {
                System.out.println("3、休眠被終止");
                return; // 返回撥用處
            }
            System.out.println("4、run()方法正常結束");
        }
    }
}複製程式碼

終止執行緒

Thread 中的 stop 方法有缺陷,已廢棄。

安全地終止執行緒有兩種方法:

  1. 中斷狀態是執行緒的一個標識位,而中斷操作是一種簡便的執行緒間互動方式,而這種互動方式最適合用來取消或停止任務。
  2. 還可以利用一個 boolean 變數來控制是否需要停止任務並終止該執行緒。
public class ThreadStopDemo03 {

    public static void main(String[] args) throws Exception {
        MyTask one = new MyTask();
        Thread countThread = new Thread(one, "CountThread");
        countThread.start();
        // 睡眠1秒,main執行緒對CountThread進行中斷,使CountThread能夠感知中斷而結束
        TimeUnit.SECONDS.sleep(1);
        countThread.interrupt();
        MyTask two = new MyTask();
        countThread = new Thread(two, "CountThread");
        countThread.start();
        // 睡眠1秒,main執行緒對Runner two進行取消,使CountThread能夠感知on為false而結束
        TimeUnit.SECONDS.sleep(1);
        two.cancel();
    }

    private static class MyTask implements Runnable {

        private long i;
        private volatile boolean on = true;

        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("Count i = " + i);
        }

        void cancel() {
            on = false;
        }
    }
}複製程式碼

Thread 中的重要方法

  • run - 執行緒的執行實體。
  • start - 執行緒的啟動方法。
  • setNamegetName - 可以通過 setName()、 getName() 來設定、獲取執行緒名稱。
  • setPrioritygetPriority - 在 Java 中,所有執行緒在執行前都會保持在就緒狀態,那麼此時,哪個執行緒優先順序高,哪個執行緒就有可能被先執行。可以通過 setPriority、getPriority 來設定、獲取執行緒優先順序。
  • setDaemonisDaemon - 可以使用 setDaemon() 方法設定執行緒為守護執行緒;可以使用 isDaemon() 方法判斷執行緒是否為守護執行緒。
  • isAlive - 可以通過 isAlive 來判斷執行緒是否啟動。
  • interrupt - 當一個執行緒執行時,另一個執行緒可以直接通過 interrupt() 方法中斷其執行狀態。
  • join - 使用 join() 方法讓一個執行緒強制執行,執行緒強制執行期間,其他執行緒無法執行,必須等待此執行緒完成之後才可以繼續執行。
  • Thread.sleep - 使用 Thread.sleep() 方法即可實現休眠。
  • Thread.yield - 可以使用 Thread.yield() 方法將一個執行緒的操作暫時讓給其他執行緒執行。

設定/獲取執行緒名稱

在 Thread 類中可以通過 setName()getName() 來設定、獲取執行緒名稱。

public class ThreadNameDemo {

    public static void main(String[] args) {
        MyThread mt = new MyThread(); // 例項化Runnable子類物件
        new Thread(mt).start(); // 系統自動設定執行緒名稱
        new Thread(mt, "執行緒-A").start(); // 手工設定執行緒名稱
        Thread t = new Thread(mt); // 手工設定執行緒名稱
        t.setName("執行緒-B");
        t.start();
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + "執行,i = " + i); // 取得當前執行緒的名字
            }
        }
    }
}複製程式碼

判斷執行緒是否啟動

在 Thread 類中可以通過 isAlive() 來判斷執行緒是否啟動。

public class ThreadAliveDemo {

    public static void main(String[] args) {
        MyThread mt = new MyThread(); // 例項化Runnable子類物件
        Thread t = new Thread(mt, "執行緒"); // 例項化Thread物件
        System.out.println("執行緒開始執行之前 --> " + t.isAlive()); // 判斷是否啟動
        t.start(); // 啟動執行緒
        System.out.println("執行緒開始執行之後 --> " + t.isAlive()); // 判斷是否啟動
        for (int i = 0; i < 3; i++) {
            System.out.println(" main執行 --> " + i);
        }
        // 以下的輸出結果不確定
        System.out.println("程式碼執行之後 --> " + t.isAlive()); // 判斷是否啟動

    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + "執行,i = " + i);
            }
        }
    }
}複製程式碼

守護執行緒

在 Java 程式中,只要前臺有一個執行緒在執行,則整個 Java 程式就不會消失,所以此時可以設定一個守護執行緒,這樣即使 Java 程式結束了,此守護執行緒依然會繼續執行。可以使用 setDaemon() 方法設定執行緒為守護執行緒;可以使用 isDaemon() 方法判斷執行緒是否為守護執行緒。

public class ThreadDaemonDemo {

    public static void main(String[] args) {
        Thread t = new Thread(new MyThread(), "執行緒");
        t.setDaemon(true); // 此執行緒在後臺執行
        System.out.println("執行緒 t 是否是守護程式:" + t.isDaemon());
        t.start(); // 啟動執行緒
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            while (true) {
                System.out.println(Thread.currentThread().getName() + "在執行。");
            }
        }
    }
}複製程式碼

設定/獲取執行緒優先順序

在 Java 中,所有執行緒在執行前都會保持在就緒狀態,那麼此時,哪個執行緒優先順序高,哪個執行緒就有可能被先執行。

public class ThreadPriorityDemo {

    public static void main(String[] args) {
        System.out.println("主方法的優先順序:" + Thread.currentThread().getPriority());
        System.out.println("MAX_PRIORITY = " + Thread.MAX_PRIORITY);
        System.out.println("NORM_PRIORITY = " + Thread.NORM_PRIORITY);
        System.out.println("MIN_PRIORITY = " + Thread.MIN_PRIORITY);

        Thread t1 = new Thread(new MyThread(), "執行緒A"); // 例項化執行緒物件
        Thread t2 = new Thread(new MyThread(), "執行緒B"); // 例項化執行緒物件
        Thread t3 = new Thread(new MyThread(), "執行緒C"); // 例項化執行緒物件
        t1.setPriority(Thread.MIN_PRIORITY); // 優先順序最低
        t2.setPriority(Thread.MAX_PRIORITY); // 優先順序最低
        t3.setPriority(Thread.NORM_PRIORITY); // 優先順序最低
        t1.start(); // 啟動執行緒
        t2.start(); // 啟動執行緒
        t3.start(); // 啟動執行緒
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(500); // 執行緒休眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 取得當前執行緒的名字
                String out =
                    Thread.currentThread().getName() + ",優先順序:" + Thread.currentThread().getPriority() + ",執行:i = " + i;
                System.out.println(out);
            }
        }
    }
}複製程式碼

執行緒間通訊

wait/notify/notifyAll

wait、notify、notifyAll 是 Object 類中的方法。

  • wait - 執行緒自動釋放其佔有的物件鎖,並等待 notify。
  • notify - 喚醒一個正在 wait 當前物件鎖的執行緒,並讓它拿到物件鎖。
  • notifyAll - 喚醒所有正在 wait 前物件鎖的執行緒。

生產者、消費者示例:

public class ThreadWaitNotifyDemo02 {

    private static final int QUEUE_SIZE = 10;
    private static final PriorityQueue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE);

    public static void main(String[] args) {
        new Producer("生產者A").start();
        new Producer("生產者B").start();
        new Consumer("消費者A").start();
        new Consumer("消費者B").start();
    }

    static class Consumer extends Thread {

        Consumer(String name) {
            super(name);
        }

        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    while (queue.size() == 0) {
                        try {
                            System.out.println("佇列空,等待資料");
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notifyAll();
                        }
                    }
                    queue.poll(); // 每次移走隊首元素
                    queue.notifyAll();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 從佇列取走一個元素,佇列當前有:" + queue.size() + "個元素");
                }
            }
        }
    }

    static class Producer extends Thread {

        Producer(String name) {
            super(name);
        }

        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    while (queue.size() == QUEUE_SIZE) {
                        try {
                            System.out.println("佇列滿,等待有空餘空間");
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notifyAll();
                        }
                    }
                    queue.offer(1); // 每次插入一個元素
                    queue.notifyAll();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 向佇列取中插入一個元素,佇列當前有:" + queue.size() + "個元素");
                }
            }
        }
    }
}複製程式碼

執行緒的禮讓

線上程操作中,可以使用 Thread.yield() 方法將一個執行緒的操作暫時讓給其他執行緒執行。

public class ThreadYieldDemo {

    public static void main(String[] args) {
        MyThread t = new MyThread();
        new Thread(t, "執行緒A").start();
        new Thread(t, "執行緒B").start();
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "執行,i = " + i);
                if (i == 2) {
                    System.out.print("執行緒禮讓:");
                    Thread.yield();
                }
            }
        }
    }
}複製程式碼

執行緒的強制執行

線上程操作中,可以使用 join() 方法讓一個執行緒強制執行,執行緒強制執行期間,其他執行緒無法執行,必須等待此執行緒完成之後才可以繼續執行。

public class ThreadJoinDemo {

    public static void main(String[] args) {
        MyThread mt = new MyThread(); // 例項化Runnable子類物件
        Thread t = new Thread(mt, "mythread"); // 例項化Thread物件
        t.start(); // 啟動執行緒
        for (int i = 0; i < 50; i++) {
            if (i > 10) {
                try {
                    t.join(); // 執行緒強制執行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Main 執行緒執行 --> " + i);
        }
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName() + " 執行,i = " + i); // 取得當前執行緒的名字
            }
        }
    }
}複製程式碼

執行緒的休眠

直接使用 Thread.sleep() 方法即可實現休眠。

public class ThreadSleepDemo {

    public static void main(String[] args) {
        new Thread(new MyThread("執行緒A", 1000)).start();
        new Thread(new MyThread("執行緒A", 2000)).start();
        new Thread(new MyThread("執行緒A", 3000)).start();
    }

    static class MyThread implements Runnable {

        private String name;
        private int time;

        private MyThread(String name, int time) {
            this.name = name; // 設定執行緒名稱
            this.time = time; // 設定休眠時間
        }

        @Override
        public void run() {
            try {
                Thread.sleep(this.time); // 休眠指定的時間
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.name + "執行緒,休眠" + this.time + "毫秒。");
        }
    }
}複製程式碼

ThreadLocal

ThreadLocal,很多地方叫做執行緒本地變數,也有些地方叫做執行緒本地儲存,其實意思差不多。可能很多朋友都知道 ThreadLocal 為變數在每個執行緒中都建立了一個副本,那麼每個執行緒可以訪問自己內部的副本變數。

原始碼

ThreadLocal 的主要方法:

public class ThreadLocal<T> {
    public T get() {}
	public void remove() {}
	public void set(T value) {}
	public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {}
}複製程式碼
  • get()方法是用來獲取 ThreadLocal 在當前執行緒中儲存的變數副本。
  • set()用來設定當前執行緒中變數的副本。
  • remove()用來移除當前執行緒中變數的副本。
  • initialValue()是一個 protected 方法,一般是用來在使用時進行重寫的,它是一個延遲載入方法,下面會詳細說明。
get() 原始碼實現

get 原始碼

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}複製程式碼
  1. 取得當前執行緒。
  2. 通過 getMap() 方法獲取 ThreadLocalMap。
  3. 成功,返回 value;失敗,返回 setInitialValue()。
ThreadLocalMap 原始碼實現

ThreadLocalMap 原始碼

ThreadLocalMap 是 ThreadLocal 的一個內部類。

ThreadLocalMap 的 Entry 繼承了 WeakReference,並且使用 ThreadLocal 作為鍵值。

setInitialValue 原始碼實現
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}複製程式碼

如果 map 不為空,就設定鍵值對;為空,再建立 Map,看一下 createMap 的實現:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}複製程式碼
ThreadLocal 原始碼小結

至此,可能大部分朋友已經明白了 ThreadLocal 是如何為每個執行緒建立變數的副本的:

  1. 在每個執行緒 Thread 內部有一個 ThreadLocal.ThreadLocalMap 型別的成員變數 threadLocals,這個 threadLocals 就是用來儲存實際的變數副本的,鍵值為當前 ThreadLocal 變數,value 為變數副本(即 T 型別的變數)。
  2. 在 Thread 裡面,threadLocals 為空,當通過 ThreadLocal 變數呼叫 get()方法或者 set()方法,就會對 Thread 類中的 threadLocals 進行初始化,並且以當前 ThreadLocal 變數為鍵值,以 ThreadLocal 要儲存的副本變數為 value,存到 threadLocals。
  3. 在當前執行緒裡面,如果要使用副本變數,就可以通過 get 方法在 threadLocals 裡面查詢。

示例

ThreadLocal 最常見的應用場景為用於解決資料庫連線、Session 管理等問題。

示例 - 資料庫連線

private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
    return DriverManager.getConnection(DB_URL);
}
};

public static Connection getConnection() {
return connectionHolder.get();
}複製程式碼

示例 - Session 管理

private static final ThreadLocal threadSession = new ThreadLocal();

public static Session getSession() throws InfrastructureException {
    Session s = (Session) threadSession.get();
    try {
        if (s == null) {
            s = getSessionFactory().openSession();
            threadSession.set(s);
        }
    } catch (HibernateException ex) {
        throw new InfrastructureException(ex);
    }
    return s;
}複製程式碼

管道輸入/輸出流

管道輸入/輸出流和普通的檔案輸入/輸出流或者網路輸入/輸出流不同之處在於,它主要用於執行緒之間的資料傳輸,而傳輸的媒介為記憶體。 管道輸入/輸出流主要包括瞭如下 4 種具體實現:PipedOutputStream、PipedInputStream、PipedReader 和 PipedWriter,前兩種面向位元組,而後兩種面向字元。

public class Piped {

    public static void main(String[] args) throws Exception {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
        // 將輸出流和輸入流進行連線,否則在使用時會丟擲IOException
        out.connect(in);
        Thread printThread = new Thread(new Print(in), "PrintThread");
        printThread.start();
        int receive = 0;
        try {
            while ((receive = System.in.read()) != -1) {
                out.write(receive);
            }
        } finally {
            out.close();
        }
    }

    static class Print implements Runnable {

        private PipedReader in;

        Print(PipedReader in) {
            this.in = in;
        }

        public void run() {
            int receive = 0;
            try {
                while ((receive = in.read()) != -1) {
                    System.out.print((char) receive);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}複製程式碼

FAQ

start() 和 run() 有什麼區別?可以直接呼叫 Thread 類的 run() 方法麼?

run() 方法是執行緒的執行體。

start() 方法會啟動執行緒,然後 JVM 會讓這個執行緒去執行 run() 方法。

可以直接呼叫 Thread 類的 run() 方法麼?

  • 可以。但是如果直接呼叫 Thread 的 run()方法,它的行為就會和普通的方法一樣。
  • 為了在新的執行緒中執行我們的程式碼,必須使用 Thread.start()方法。

sleep()、yield()、join() 方法有什麼區別?為什麼 sleep()和 yield()方法是靜態的?

  • yield()
    • yield() 方法可以讓當前正在執行的執行緒暫停,但它不會阻塞該執行緒,它只是將該執行緒從 Running 狀態轉入 Runnable 狀態。
    • 當某個執行緒呼叫了 yield() 方法暫停之後,只有優先順序與當前執行緒相同,或者優先順序比當前執行緒更高的處於就緒狀態的執行緒才會獲得執行的機會。
  • sleep()
    • sleep() 方法需要指定等待的時間,它可以讓當前正在執行的執行緒在指定的時間內暫停執行,進入 Blocked 狀態。
    • 該方法既可以讓其他同優先順序或者高優先順序的執行緒得到執行的機會,也可以讓低優先順序的執行緒得到執行機會。
    • 但是, sleep() 方法不會釋放“鎖標誌”,也就是說如果有 synchronized 同步塊,其他執行緒仍然不能訪問共享資料。
  • join()
    • join() 方法會使當前執行緒轉入 Blocked 狀態,等待呼叫 join() 方法的執行緒結束後才能繼續執行。

參考閱讀:Java 執行緒中 yield 與 join 方法的區別 參考閱讀:sleep(),wait(),yield()和 join()方法的區別

為什麼 sleep() 和 yield() 方法是靜態的

  • Thread 類的 sleep() 和 yield() 方法將處理 Running 狀態的執行緒。所以在其他處於非 Running 狀態的執行緒上執行這兩個方法是沒有意義的。這就是為什麼這些方法是靜態的。它們可以在當前正在執行的執行緒中工作,並避免程式設計師錯誤的認為可以在其他非執行執行緒呼叫這些方法。

Java 的執行緒優先順序如何控制?高優先順序的 Java 執行緒一定先執行嗎?

  • Java 中的執行緒優先順序如何控制
    • Java 中的執行緒優先順序的範圍是 [1,10],一般來說,高優先順序的執行緒在執行時會具有優先權。可以通過 thread.setPriority(Thread.MAX_PRIORITY) 的方式設定,預設優先順序為 5。
  • 高優先順序的 Java 執行緒一定先執行嗎
    • 即使設定了執行緒的優先順序,也無法保證高優先順序的執行緒一定先執行
    • 原因:這是因為執行緒優先順序依賴於作業系統的支援,然而,不同的作業系統支援的執行緒優先順序並不相同,不能很好的和 Java 中執行緒優先順序一一對應。
    • 結論:Java 執行緒優先順序控制並不可靠。

什麼是守護執行緒?為什麼要用守護執行緒?如何建立守護執行緒?

  • 什麼是守護執行緒
    • 守護執行緒(Daemon Thread)是在後臺執行並且不會阻止 JVM 終止的執行緒。
    • 與守護執行緒(Daemon Thread)相反的,叫使用者執行緒(User Thread),也就是非守護執行緒。
  • 為什麼要用守護執行緒
    • 守護執行緒的優先順序比較低,用於為系統中的其它物件和執行緒提供服務。典型的應用就是垃圾回收器。
  • 如何建立守護執行緒
    • 使用 thread.setDaemon(true) 可以設定 thread 執行緒為守護執行緒。
    • 注意點:
      • 正在執行的使用者執行緒無法設定為守護執行緒,所以 thread.setDaemon(true) 必須在 thread.start() 之前設定,否則會丟擲 llegalThreadStateException 異常;
      • 一個守護執行緒建立的子執行緒依然是守護執行緒。
      • 不要認為所有的應用都可以分配給 Daemon 來進行服務,比如讀寫操作或者計算邏輯。

參考閱讀:Java 中守護執行緒的總結

為什麼執行緒通訊的方法 wait(), notify()和 notifyAll()被定義在 Object 類裡?

Java 的每個物件中都有一個鎖(monitor,也可以成為監視器) 並且 wait(),notify()等方法用於等待物件的鎖或者通知其他執行緒物件的監視器可用。在 Java 的執行緒中並沒有可供任何物件使用的鎖和同步器。這就是為什麼這些方法是 Object 類的一部分,這樣 Java 的每一個類都有用於執行緒間通訊的基本方法

為什麼 wait(), notify()和 notifyAll()必須在同步方法或者同步塊中被呼叫?

當一個執行緒需要呼叫物件的 wait()方法的時候,這個執行緒必須擁有該物件的鎖,接著它就會釋放這個物件鎖並進入等待狀態直到其他執行緒呼叫這個物件上的 notify()方法。同樣的,當一個執行緒需要呼叫物件的 notify()方法時,它會釋放這個物件的鎖,以便其他在等待的執行緒就可以得到這個物件鎖。由於所有的這些方法都需要執行緒持有物件的鎖,這樣就只能通過同步來實現,所以他們只能在同步方法或者同步塊中被呼叫。


相關文章