Java併發編程——線程基礎查漏補缺

Thread

使用Java的同窗對Thread應該不陌生了,線程的建立和啓動等這裏就不講了,這篇主要講幾個容易被忽視的方法以及線程狀態遷移。java

wait/notify/notifyAll

首先咱們要明白這三個方法是定義在Object類中,他們起到的做用就是容許線程就資源的鎖定狀態進行通訊。這裏所說的資源通常就是指的咱們常說的共享對象了,也就是說針對共享對象的鎖定狀態能夠經過wait/notify/notifyAll來進行通訊。咱們先看下如何使用的,並對相應原理進行展開。編程

wait


wait方法告訴調用線程放棄鎖定並進入休眠狀態,直到其餘某個線程進入同一個監視器(monitor)並調用notify方法。wait方法在等待以前釋放鎖,並在wait方法返回以前從新獲取鎖。wait方法實際上和同步鎖緊密集成,補充同步機制沒法直接實現的功能。
須要注意到wait方法在jdk源碼中是final而且是native的本地方法,咱們沒法去覆蓋此方法。
調用wait通常的方式以下:segmentfault

synchronized(lockObject) {
    while(!condition) {
        lockObject.wait();
    }
    // 這裏進行相應處理;
}

注意這裏使用while進行條件判斷而沒有使用if進行條件判斷,緣由是這裏有個很重要的點容易被忽視,下面來自官方的建議:安全

應該在循環中檢查等待條件,緣由是處於等待狀態的線程可能會收到錯誤的警報和僞喚醒,若是不在循環條件中等待,程序就會在沒有知足結束條件的狀況下退出。

notify


notify方法喚醒了同一個對象上調用wait的線程。這裏要注意notify並無放棄對資源的鎖定,他告訴等待的線程能夠喚醒,可是做用在notify上synchronized同步塊完成以前,其實是不會放棄鎖。所以,若是通知線程在同步塊內,調用notify方法後,須要在進行10s的其餘操做,那麼等待的線程將會再至少等待10s。
notify通常的使用方式以下:併發

synchronized(lockObject) {
    // 肯定條件
    lockObject.notify();
    // 若是須要能夠加任意代碼
}

notifyAll


notifyAll會喚醒在同一個對象上調用wait方法的全部線程。在大多數狀況下優先級最高的線程將被執行,可是也是沒法徹底保證會是這樣。其餘的與notify相同。app

使用例子


下面的代碼示例實現了隊列空和滿時線程阻塞已經非空非滿時的通知:ide

生產者:測試

class Producer implements Runnable {
    private final List<Integer> taskQueue;
    private final int MAX_CAPACITY;

    public Producer(List<Integer> sharedQueue, int size) {
        this.taskQueue = sharedQueue;
        this.MAX_CAPACITY = size;
    }

    @Override
    public void run() {
        int counter = 0;
        while (true) {
            try {
                produce(counter++);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    private void produce(int i) throws InterruptedException {
        synchronized (taskQueue) {
            while (taskQueue.size() == MAX_CAPACITY) {
                System.out.println("隊列已滿,線程" + Thread.currentThread().getName() + "進入等待,隊列長度:" + taskQueue.size());
                taskQueue.wait();
            }

            Thread.sleep(1000);
            taskQueue.add(i);
            System.out.println("生產:" + i);
            taskQueue.notifyAll();
        }
    }
}

消費者:this

class Consumer implements Runnable {
    private final List<Integer> taskQueue;

    public Consumer(List<Integer> sharedQueue) {
        this.taskQueue = sharedQueue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                consume();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    private void consume() throws InterruptedException {
        synchronized (taskQueue) {
            while (taskQueue.isEmpty()) {
                System.out.println("隊列已空,線程" + Thread.currentThread().getName() + "進入等待,隊列長度:" + taskQueue.size());
                taskQueue.wait();
            }
            Thread.sleep(1000);
            int i = (Integer) taskQueue.remove(0);
            System.out.println("消費:" + i);
            taskQueue.notifyAll();
        }
    }
}

測試代碼:spa

public class ProducerConsumerExampleWithWaitAndNotify {
    public static void main(String[] args) {
        List<Integer> taskQueue = new ArrayList<>();
        int MAX_CAPACITY = 5;
        Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "Producer");
        Thread tConsumer = new Thread(new Consumer(taskQueue), "Consumer");
        tProducer.start();
        tConsumer.start();
    }
}

部分輸出以下:

生產:0
生產:1
生產:2
生產:3
生產:4
隊列已滿,線程Producer進入等待,隊列長度:5
消費:0
消費:1
消費:2
消費:3
消費:4
隊列已空,線程Consumer進入等待,隊列長度:0

yield/join

yield


從字面意思理解yield能夠是謙讓、放棄、屈服、投降的意思。一個要「謙讓」的線程實際上是在告訴虛擬機他願意讓其餘線程安排到他的前面,這代表他沒有說明重要的事情要作了。注意了這只是個提示,並不能保證能起到任何效果。
yield在Thread.java中定義以下:

/**
 * A hint to the scheduler that the current thread is willing to yield
 * its current use of a processor. The scheduler is free to ignore this
 * hint.
 *  * <p> Yield is a heuristic attempt to improve relative progression
 * between threads that would otherwise over-utilise a CPU. Its use
 * should be combined with detailed profiling and benchmarking to
 * ensure that it actually has the desired effect.
 *  * <p> It is rarely appropriate to use this method. It may be useful
 * for debugging or testing purposes, where it may help to reproduce
 * bugs due to race conditions. It may also be useful when designing
 * concurrency control constructs such as the ones in the
 * {@link java.util.concurrent.locks} package.
*/
public static native void yield();

從這裏面咱們總結出一些重點(有關線程狀態後面會講到):

  • yield方法是一個靜態的而且是native的方法。
  • yield告訴當前執行的線程爲線程池中其餘具備相同優先級的線程提供機會。
  • 不能保證yield會當即使當前正在執行的線程處於可運行狀態。
  • 他只能使得線程從運行狀態變成可運行狀態,而沒法作其餘狀態改變。

yield使用例子:

public class YieldExample {
   public static void main(String[] args) {
      Thread producer = new Producer();
      Thread consumer = new Consumer();
       
      producer.setPriority(Thread.MIN_PRIORITY); // 最低優先級
      consumer.setPriority(Thread.MAX_PRIORITY); // 最高優先級
       
      producer.start();
      consumer.start();
   }
}
 
class Producer extends Thread {
   public void run() {
      for (int i = 0; i < 5; i++) {
         System.out.println("生產者 : 生產 " + i);
         Thread.yield();
      }
   }
}
 
class Consumer extends Thread {
   public void run() {
      for (int i = 0; i < 5; i++) {
         System.out.println("消費者 : 消費 " + i);
         Thread.yield();
      }
   }
}

當註釋兩個「Thread.yield();」時輸出:

消費者 : 消費 0
消費者 : 消費 1
消費者 : 消費 2
消費者 : 消費 3
消費者 : 消費 4
生產者 : 生產 0
生產者 : 生產 1
生產者 : 生產 2
生產者 : 生產 3
生產者 : 生產 4

當不註釋兩個「Thread.yield();」時輸出:

生產者 : 生產 0
消費者 : 消費 0
生產者 : 生產 1
消費者 : 消費 1
生產者 : 生產 2
消費者 : 消費 2
生產者 : 生產 3
消費者 : 消費 3
生產者 : 生產 4
消費者 : 消費 4

join


join方法用於將線程當前執行點鏈接到另外一個線程的執行結束,這樣這個線程就不會開始運行直到另外一個線程結束。在Thread實例上調用join,則當前運行的線程將會阻塞,直到這個Thread實例完成執行。
簡要摘抄Thread.java源碼中join的定義:

// Waits for this thread to die.
public final void join() throws InterruptedException

join還有能夠傳入時間參數的重載方法,這個能夠時join的效果在特定時間後無效。當達到超時時間時,主線程和taskThread是一樣可能的執行者候選。可是join和sleep同樣,依賴於OS進行計時,不該該假定恰好等待指定的時間。
join和sleep同樣也經過InterruptedException來響應中斷。
join使用示例:

public class JoinExample {
   public static void main(String[] args) throws InterruptedException {
      Thread t = new Thread(new Runnable() {
          public void run() {
              System.out.println("第一個任務啓動");
              System.out.println("睡眠2s");
              try {
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("第一個任務完成");
          }
      });
      Thread t1 = new Thread(new Runnable() {
          public void run() {
              System.out.println("第二個任務完成");
          }
      });
      t.start();
      t.join();
      t1.start();
   }
}

輸出結果:

第一個任務啓動
睡眠2s
第一個任務完成
第二個任務完成

join原理分析


join在Thread.java中有三個重載方法:

public final void join() throws InterruptedException

public final synchronized void join(long millis) throws InterruptedException

public final synchronized void join(long millis, int nanos) throws InterruptedException

查看源碼能夠得知最終的實現核心部分都在join(long millis)中,咱們來分析下這個方法源碼:

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

首先能夠看到這個方法是使用synchronized修飾的同步方法,從這個方法的源碼能夠看出join的核心就是使用wait來實現的,而外部條件就是isAlive(),能夠判定,在非isAlive()時會進行notify。

線程狀態

在Thread.java的源代碼中就體現出了六種狀態:

/**
     * A thread state.  A thread can be in one of the following states:
     * <ul>
     * <li>{@link #NEW}<br>
     *     A thread that has not yet started is in this state.
     *     </li>
     * <li>{@link #RUNNABLE}<br>
     *     A thread executing in the Java virtual machine is in this state.
     *     </li>
     * <li>{@link #BLOCKED}<br>
     *     A thread that is blocked waiting for a monitor lock
     *     is in this state.
     *     </li>
     * <li>{@link #WAITING}<br>
     *     A thread that is waiting indefinitely for another thread to
     *     perform a particular action is in this state.
     *     </li>
     * <li>{@link #TIMED_WAITING}<br>
     *     A thread that is waiting for another thread to perform an action
     *     for up to a specified waiting time is in this state.
     *     </li>
     * <li>{@link #TERMINATED}<br>
     *     A thread that has exited is in this state.
     *     </li>
     * </ul>
     *
     * <p>
     * A thread can be in only one state at a given point in time.
     * These states are virtual machine states which do not reflect
     * any operating system thread states.
     *
     * @since   1.5
     * @see #getState
     */
    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

通常咱們用以下圖來表示狀態遷移,注意相關方法。(注意:其中RUNNING和READY是沒法直接獲取的狀態。)
線程狀態

下一篇:Java併發編程——線程安全性深層緣由

相關文章
相關標籤/搜索