1. 程式人生 > >執行緒最少必要知識(一)

執行緒最少必要知識(一)

你真的瞭解執行緒嗎?建立執行緒的常用方式有哪些?為什麼不能重複呼叫 Start 方法?什麼是單繼承的侷限?生產者與消費者如何實現?

1. 程序與執行緒

1.1 程序

程序是系統資源(CPU、記憶體等)分配的最小單位,它是程式執行時的一個例項。

通常情況下,程序和程式、應用可以看作是同一個概念,但一個程式可能有多個程序,例如,你可以在一臺電腦上同時開啟多個 QQ。開啟的每一個 QQ 都對應一個程序,但所有的這些程序都屬於程式 QQ。如下圖:

每一個程式執行時系統都會為其建立一個程序,併為其分配資源,把該程序放入程序就緒佇列,當程序排程器選中它時,就會為它分配 CPU 時間,此時程式才開始真正執行。因此,從微觀上來看,程式的每一次執行,都可以看作是 “程式碼載入→程式碼執行→執行完畢”

一個完整的過程 。

1.2 執行緒

執行緒是 CPU 排程的最小單位,是“輕量級”的程序。

1.3 程序與執行緒的關係

  • 程序是執行緒的載體,一個程序至少有一個執行緒
  • 系統分配給每一個程序的資源在程序間是不共享,但每一個程序中的所有執行緒共享系統分配給該程序的資源
  • 程序的建立資源消耗較大,執行緒的建立幾乎不需要任何資源,因為它所需要的資源,在建立程序的時候已經載入進來了
  • 相比於程序間通訊,執行緒間通訊十分簡單

舉個生活中的例子,程序相當於雙向四車道的馬路,執行緒相當於這條馬路上的一條車道。

2. 常用的建立執行緒的方法

通常情況下,建立執行緒的方法有兩種:

  • 繼承 Thread 類
  • 實現 Runnable 介面

2.1 繼承 Thread 類

2.1.1 語法
class 類名稱 extends Thread{
    屬性...;
    方法...;
    public void run(){
        執行緒主體;
    }
}
2.1.2 例項
//原始碼:
public class ThreadByExtends_201810252239 extends Thread {
	
	public ThreadByExtends_201810252239() {}
	
	public ThreadByExtends_201810252239(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王蓋地虎");
		t1.start();
	}
	
}

//執行結果:
THREAD-A-天王蓋地虎  0
THREAD-A-天王蓋地虎  1
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  4

2.2 實現 Runnable 介面

2.2.1 語法
class 類名稱 implements Runnable{
    屬性...;
    方法...;
    public void run(){
        執行緒主體;
    }
}
2.2.2 例項
//原始碼:
public class ThreadByImplements_201810260617 implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByImplements_201810260617 r1 = new ThreadByImplements_201810260617();
		Thread t1 = new Thread(r1, "THREAD-A-小雞燉蘑菇");
		t1.start();
	}

}

//執行結果:
THREAD-A-小雞燉蘑菇  0
THREAD-A-小雞燉蘑菇  1
THREAD-A-小雞燉蘑菇  2
THREAD-A-小雞燉蘑菇  3
THREAD-A-小雞燉蘑菇  4

2.3 相關問題

2.3.1 通過繼承 Thread 類建立執行緒時,為什麼一定要覆寫 run 方法?

檢視 run 方法在 Thread 類中的定義:

//原始碼:
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

由 run 方法定義可知,預設情況下,run 方法的執行最終呼叫的是 runnable 介面中的方法,所以如果通過繼承 Thread 實現執行緒的時候不覆寫 run 方法,那這個執行緒最終什麼事也沒做。

2.3.2 啟動執行緒的時候,為什麼呼叫的是 start 方法而不是 run 方法?能直接呼叫 run 方法嗎?

檢視 start 方法在 Thread 類中的定義:

//原始碼:
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 方法定義可知,start 方法最終呼叫的是 start0 方法,在 start0 方法宣告處使用了 native 關鍵字,該關鍵字表示呼叫本機作業系統函式,因此此處執行緒的啟動必須通過 start 方法。

如果直接呼叫 run 方法會出現什麼情況?

//原始碼:
public class ThreadByExtends_201810261951 extends Thread{

	public ThreadByExtends_201810261951() {}
	
	public ThreadByExtends_201810261951(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810261951 t1 = new ThreadByExtends_201810261951("THREAD-A-寶塔鎮河妖");
		t1.start();
		ThreadByExtends_201810261951 t2 = new ThreadByExtends_201810261951("THREAD-B-鐵鍋燉大鵝");
		t2.run();
	}

}

//執行結果:
main  0
main  1
main  2
THREAD-A-寶塔鎮河妖  0
THREAD-A-寶塔鎮河妖  1
main  3
main  4
THREAD-A-寶塔鎮河妖  2
THREAD-A-寶塔鎮河妖  3
THREAD-A-寶塔鎮河妖  4

由執行結果可知,當直接呼叫 run 方法的時候,實際上和普通方法的呼叫並沒有任何區別——方法的執行是在 run 方法的呼叫執行緒,而不是子執行緒。

2.3.3 為什麼不能重複呼叫 start 方法?

如果直接呼叫兩次 start 方法會出現什麼情況呢?

//原始碼:
public ThreadByExtends_201810262030() {}
	
	public ThreadByExtends_201810262030(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810262030 t1 = new ThreadByExtends_201810262030("THREAD-A-寶塔鎮河妖");
		t1.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t1.start();
	}

}

//執行結果:
THREAD-A-寶塔鎮河妖  0
THREAD-A-寶塔鎮河妖  1
THREAD-A-寶塔鎮河妖  2
THREAD-A-寶塔鎮河妖  3
THREAD-A-寶塔鎮河妖  4
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.start(Thread.java:705)
	at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810262030.main(ThreadByExtends_201810262030.java:42)

由執行結果可知,當直接呼叫兩次 start 方法時,程式會丟擲 IllegalThreadStateException 異常。

接下來,看看 start 方法在 Thread 類中是如何定義的:

//原始碼:
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 方法定義可知,當 threadStatus != 0 時,程式會丟擲 IllegalThreadStateException()。那 threadStatus 對應的是什麼呢?檢視 threadStatus 在 Thread 中的定義:

//原始碼:
/* Java thread status for tools,
 * initialized to indicate thread 'not yet started'
 */

private volatile int threadStatus = 0;

由 threadStatus 變數的註釋可知,當執行緒還沒有啟動的時候,threadStatus = 0。另外,由 start 方法定義中的註釋可知,當 threadStatus = 0 時,執行緒對應的狀態是 NEW。那這個 NEW 是在哪裡定義的呢?檢視 Thread 類定義可知,NEW 是列舉類 State 的一個物件,列舉類 State 定義如下:

//原始碼:
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;
}

由列舉類 State 定義可知,當 Thread 啟動之後,便會進入其他狀態。當執行緒執行完畢的時候,執行緒的狀態會變成 TERMINATED 而不是 NEW,因此,重複呼叫 start 方法時,程式會拋異常。

為了驗證上面的分析,將之前的程式碼略作修改:

//原始碼:
public class ThreadByExtends_201810262030 extends Thread{

    public ThreadByExtends_201810262030() {}
	
	public ThreadByExtends_201810262030(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810262030 t1 = new ThreadByExtends_201810262030("THREAD-A-寶塔鎮河妖");
		System.out.println(t1.getState());
		t1.start();
		System.out.println(t1.getState());
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(t1.getState());
		t1.start();
	}

}

//執行結果:
NEW
RUNNABLE
THREAD-A-寶塔鎮河妖  0
THREAD-A-寶塔鎮河妖  1
THREAD-A-寶塔鎮河妖  2
THREAD-A-寶塔鎮河妖  3
THREAD-A-寶塔鎮河妖  4
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.start(Thread.java:705)
	at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810262030.main(ThreadByExtends_201810262030.java:45)

由執行結果可知,當執行緒還沒有呼叫 start 方法的時候,此時 State 對應的是 NEW;當執行緒呼叫 start 方法之後,此時 State 對應的是 RUNNABLE;當執行緒執行完畢之後,此時 State 對應的是 TERMINATED,即:當再次呼叫 start 方法時,State 對應的不是 NEW,故拋異常。

2.3.4 兩種建立執行緒方式的聯絡及區別
2.3.4.1 聯絡

觀察 Thread 類部分程式碼:

//原始碼:
public class Thread implements Runnable {
…
/* What will be run. */
private Runnable target;
…

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
    …
    this.target = target;
    …
}

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

由 Thread 類定義可知,Thread 與 Runnable 實現類是代理的關係,Thread 是 Runnable 實現類的代理,這也解釋了為什麼兩種實現方式最終都需要通過 Thread 啟動執行緒。

2.3.4.2 區別
  1. 繼承 Thread 有單繼承侷限;
  2. 資源共享方式不同。

兩種建立執行緒的方式在資源共享方面區別還是挺大的,不過由於資源共享涉及到同步問題。因此,將此部分概念放到 《死鎖與同步》模組詳述。

2.3.5 多個 Thread 使用同一個 Runnable 物件,最終建立的執行緒是同一個嗎?它們之間什麼關係?多個 Thread 使用多個 Runnable 呢?
2.3.5.1 多個 Thread 單個 Runnable

多個 Thread 使用同一個 Runnable 物件時,最終會建立多個 Thread 物件。由於多個 Thread 使用的是同一個 Runnable,所以,最終多個 Thread 之間資料共享,如:

//原始碼:
public class ThreadByImplements_201810262032 implements Runnable {

	private int bandit = 10;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  殺了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
	}

	public static void main(String[] args) {
		ThreadByImplements_201810262032 r1 = new ThreadByImplements_201810262032();
		new Thread(r1,"THREAD-張飛").start();
		new Thread(r1,"THREAD-趙雲").start();
		new Thread(r1,"THREAD-呂布").start();
		new Thread(r1,"THREAD-董卓").start();
		new Thread(r1,"THREAD-孫尚香").start();
		new Thread(r1,"THREAD-華佗").start();
		new Thread(r1,"THREAD-曹操").start();
		new Thread(r1,"THREAD-夏侯惇").start();
		new Thread(r1).start();
		new Thread(r1).start();
	}

}

//執行結果:
THREAD-張飛  殺了  10  土匪  1578680387
THREAD-孫尚香  殺了  6  土匪  1578680387
THREAD-華佗  殺了  4  土匪  1578680387
THREAD-董卓  殺了  7  土匪  1578680387
THREAD-呂布  殺了  8  土匪  1578680387
THREAD-趙雲  殺了  9  土匪  1578680387
THREAD-董卓  殺了  1  土匪  1578680387
THREAD-華佗  殺了  2  土匪  1578680387
THREAD-孫尚香  殺了  3  土匪  1578680387
THREAD-張飛  殺了  5  土匪  1578680387

上面的執行結果不僅驗證了推論——多個 Thread 使用同一個 Runnable 時,多個 Thread 之間是資料共享的,同時還說明 Thread 物件的個數是由建立 Thread 物件的次數決定的,跟 Runnable 個數沒有關係。

2.3.5.2 多個 Thread 多個 Runnable

多個 Thread 使用多個 Runnable 物件時,最終也會建立多個 Thread 物件。同樣的,由於多個 Thread 沒有使用同一個 Runnable 物件,所以,最終多個 Thread 之間資料不共享,如:

//原始碼:
public class ThreadByImplements_201810262032 implements Runnable {

	private int bandit = 10;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  殺了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
	}

	public static void main(String[] args) {
		ThreadByImplements_201810262032 r1 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r2 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r3 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r4 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r5 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r6 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r7 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r8 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r9 = new ThreadByImplements_201810262032();
		ThreadByImplements_201810262032 r10 = new ThreadByImplements_201810262032();
		new Thread(r1,"THREAD-張飛").start();
		new Thread(r2,"THREAD-趙雲").start();
		new Thread(r3,"THREAD-呂布").start();
		new Thread(r4,"THREAD-董卓").start();
		new Thread(r5,"THREAD-孫尚香").start();
		new Thread(r6,"THREAD-華佗").start();
		new Thread(r7,"THREAD-曹操").start();
		new Thread(r8,"THREAD-夏侯惇").start();
		new Thread(r9).start();
		new Thread(r10).start();
	}

}

//執行結果:
THREAD-趙雲  殺了  10  土匪  258726645
THREAD-孫尚香  殺了  10  土匪  1513896901
THREAD-孫尚香  殺了  9  土匪  1513896901
THREAD-董卓  殺了  10  土匪  786514993
THREAD-呂布  殺了  10  土匪  441874245
THREAD-曹操  殺了  10  土匪  679081647
THREAD-曹操  殺了  9  土匪  679081647
THREAD-曹操  殺了  8  土匪  679081647
THREAD-張飛  殺了  10  土匪  1642210515
THREAD-曹操  殺了  7  土匪  679081647
THREAD-夏侯惇  殺了  10  土匪  1583687396
THREAD-呂布  殺了  9  土匪  441874245
THREAD-呂布  殺了  8  土匪  441874245
THREAD-董卓  殺了  9  土匪  786514993
THREAD-華佗  殺了  10  土匪  1785098644
THREAD-孫尚香  殺了  8  土匪  1513896901
…
2.3.6 什麼是單繼承侷限?

一言以蔽之:只能繼承一個父類。因為 Thread 是普通類,因此通過繼承 Thread 類建立執行緒的時候,不能再繼承其他類。相比於繼承 Thread 類建立執行緒的方法,通過實現 Runnable 建立執行緒的方法顯得更靈活,因為 Runnable 的實現類可以同時實現多個介面。

2.3.7 Thread.currentThread() 和 this.currentThread() 聯絡和區別

說實話,當我第一次被問到這個問題的時候有點懵逼,因為我也沒有仔細想過這個問題。就像你女朋友有天突然問你“如果我跟你媽同時掉進河裡,你先救誰?”一樣,只是覺得有些突兀。其實只要稍微看下 currentThread 方法在 Thread 類中的定義就可以一目瞭然了。

//原始碼:
/**
 * Returns a reference to the currently executing thread object.
 *
 * @return  the currently executing thread.
 */
public static native Thread currentThread();

這個方法就是一個靜態的呼叫本地方法的方法,沒了。很多人不懂的地方可能不在於方法的定義上,而在於它們方法的使用上。

在使用上二者的區別在於,前者能用在所有的類(Thread 類、非 Thread 類)中,後者只能用在 Thread 中,與此同時,兩者在 Thread 類中的使用效果並沒有什麼區別。

如果你還是不懂,那這樣問可能就懂了:currentThread()、this.currentThread() 和 Thread.currentThread() 有什麼區別?

//原始碼:
public class ThreadByExtend_201809052254 extends Thread {
	
	public ThreadByExtend_201809052254() {}
	
	public ThreadByExtend_201809052254(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		System.out.println(currentThread().getName());
		System.out.println(this.currentThread().getName());
		System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809052254 t1 = new ThreadByExtend_201809052254("THREAD-A-天王蓋地虎");
		t1.start();
	}
	
}

//執行結果:  
THREAD-A-天王蓋地虎
THREAD-A-天王蓋地虎
THREAD-A-天王蓋地虎


//原始碼:
public class ThreadByImplement_201809060656 implements Runnable {

	@Override
	public void run() {
		//此處如果使用此函式系統會提示 The method currentThread() is undefined for the type ThreadByImplement_201809052225
//		System.out.println(currentThread().getName());
		//此處如果使用此函式系統會提示 The method currentThread() is undefined for the type ThreadByImplement_201809052225
//		System.out.println(this.currentThread().getName());
		//此函式無影響
		System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		ThreadByImplement_201809060656 r1 = new ThreadByImplement_201809060656();
		Thread t1 = new Thread(r1,"THREAD-A-天王蓋地虎");
		Thread t2 = new Thread(r1,"THREAD-B-小雞燉蘑菇");
		Thread t3 = new Thread(r1,"THREAD-C-寶塔鎮河妖");
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-A-天王蓋地虎
THREAD-C-寶塔鎮河妖
THREAD-B-小雞燉蘑菇

這種問題產生的根本原因是基本概念不清,直接導致思考問題的出發點偏離正軌,進而導致行動錯誤,最終導致一些“神奇”的問題。

2.3.8 為什麼不能呼叫 Stop?如何停止執行緒?

檢視 stop 方法在 Thread 類中的定義:

//原始碼:
/**
 * …
 * @deprecated This method is inherently unsafe.  Stopping a thread with
 *       Thread.stop causes it to unlock all of the monitors that it
 *       has locked (as a natural consequence of the unchecked
 *       <code>ThreadDeath</code> exception propagating up the stack).  If
 *       any of the objects previously protected by these monitors were in
 *       an inconsistent state, the damaged objects become visible to
 *       other threads, potentially resulting in arbitrary behavior.  Many
 *       uses of <code>stop</code> should be replaced by code that simply
 *       modifies some variable to indicate that the target thread should
 *       stop running.  The target thread should check this variable
 *       regularly, and return from its run method in an orderly fashion
 *       if the variable indicates that it is to stop running.  If the
 *       target thread waits for long periods (on a condition variable,
 *       for example), the <code>interrupt</code> method should be used to
 *       interrupt the wait.
 */
@Deprecated
public final void stop() {}

由 stop 方法註釋(This method is inherently unsafe)可知,該方法在操作時會產生死鎖的問題,因此不建議使用。

在 stop 方法的註釋中,除了介紹為什麼該方法不建議使用之外,還介紹瞭如何停止執行緒——通過變數控制,如:

//原始碼:
public class ThreadByExtends_201810281058 extends Thread{

	private boolean mStop = false;
	private int mNumber = 0;
	
	public ThreadByExtends_201810281058() {}
	
	public ThreadByExtends_201810281058(String n) {
		super(n);
	}
	
	public boolean isStop() {
		return mStop;
	}

	public void setStop(boolean stop) {
		this.mStop = stop;
	}

	@Override
	public void run() {
		super.run();
		while (!mStop) {
			System.out.println(Thread.currentThread().getName() + "  " + (++mNumber));
		}
		System.out.println(Thread.currentThread().getName() + "  " + "停止了");
	}

	public static void main(String[] args) {
		ThreadByExtends_201810281058 t1 = new ThreadByExtends_201810281058("THREAD-A-天王蓋地虎");
		t1.start();
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t1.setStop(true);
	}

}

//執行結果:
…
THREAD-A-天王蓋地虎  26
THREAD-A-天王蓋地虎  27
THREAD-A-天王蓋地虎  28
THREAD-A-天王蓋地虎  29
THREAD-A-天王蓋地虎  30
THREAD-A-天王蓋地虎  31
THREAD-A-天王蓋地虎  停止了

由上面的示例可知,通過控制變數的方法完全可以達到停止執行緒的目的。

2.3.9 除了這種建立執行緒的方法,還有其他方法嗎?是什麼?

建立執行緒的方法除了上面提到的兩種之外,還有其他方法,如: Callable + Future

//原始碼:
public class ThreadByCallable_201810281535 implements Callable<Integer>{

	private int result;
	
	@Override
	public Integer call() throws Exception {
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
			result = i;
		}
		return result;
	}
	
	public static void main(String[] args) {
		ExecutorService executor = Executors.newCachedThreadPool();
		ThreadByCallable_201810281535 c1 = new ThreadByCallable_201810281535();
        Future<Integer> result = executor.submit(c1);
        executor.shutdown();
        try {
			System.out.println("Result " + result.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}

}

//執行結果:
pool-1-thread-1  0
pool-1-thread-1  1
pool-1-thread-1  2
pool-1-thread-1  3
pool-1-thread-1  4
Result 4

由於其他建立執行緒的方法涉及本文未提到的知識,故在此不詳述。

3. 執行緒的狀態(生命週期)

任何一個執行緒都有五種狀態,即:建立、就緒、執行、阻塞、終止。執行緒各狀態之間的轉換如下圖所示:

3.1 建立狀態

使用 new 關鍵字建立完執行緒物件之後,執行緒就進入該狀態。也就是說,系統給 Thread 物件分配了記憶體之後,這個物件就處於這個狀態了。

3.2 就緒狀態

當 Thread 物件呼叫 start 方法之後,執行緒就進入該狀態。處於這個狀態的執行緒將進入執行緒佇列排隊,等待 CPU 的排程。

3.3 執行狀態

當執行緒佇列中的執行緒被 CPU 排程的時候,執行緒就進入該狀態。此時將執行執行緒的 run 方法。

3.4 阻塞狀態

當處於執行狀態的執行緒呼叫 suspend、sleep、wait 等方法時,執行緒就進入該狀態。處於阻塞狀態的執行緒不能進入執行緒佇列排隊,只有當引起阻塞的原因消失之後,執行緒才能進入執行緒佇列排隊。

3.5 阻終止狀態

當執行緒呼叫 stop 方法或者 run 方法執行完畢之後,執行緒就進入該狀態。處於該狀態的執行緒不能再執行,詳情請參考《2.3.3 為什麼不能重複呼叫 start 方法?》。

4. 執行緒常用方法

4.1 執行緒名

4.1.1 語法
public final String getName()
4.1.2 示例
//原始碼:
public class ThreadByExtends_201810281917 extends Thread{
	
	public ThreadByExtends_201810281917() {}
	
	public ThreadByExtends_201810281917(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王蓋地虎");
		ThreadByExtends_201810252239 t2 = new ThreadByExtends_201810252239("THREAD-B-寶塔鎮河妖");
		ThreadByExtends_201810252239 t3 = new ThreadByExtends_201810252239("THREAD-C-小雞燉蘑菇");
		ThreadByExtends_201810252239 t4 = new ThreadByExtends_201810252239();
		ThreadByExtends_201810252239 t5 = new ThreadByExtends_201810252239();
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}

}

//執行結果:
THREAD-A-天王蓋地虎  0
Thread-0  0
Thread-0  1
THREAD-B-寶塔鎮河妖  0
THREAD-B-寶塔鎮河妖  1
THREAD-B-寶塔鎮河妖  2
THREAD-C-小雞燉蘑菇  0
THREAD-C-小雞燉蘑菇  1
THREAD-B-寶塔鎮河妖  3
Thread-0  2
Thread-0  3
Thread-0  4
Thread-1  0
Thread-1  1
Thread-1  2
Thread-1  3
THREAD-A-天王蓋地虎  1
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  4
Thread-1  4
THREAD-B-寶塔鎮河妖  4
THREAD-C-小雞燉蘑菇  2
THREAD-C-小雞燉蘑菇  3
THREAD-C-小雞燉蘑菇  4

4.2 狀態檢查

4.2.1 語法
public final native boolean isAlive()
4.2.2 示例
//原始碼:
public class ThreadByExtends_201810281928 extends Thread{
	
	public ThreadByExtends_201810281928() {}
	
	public ThreadByExtends_201810281928(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810252239 t1 = new ThreadByExtends_201810252239("THREAD-A-天王蓋地虎");
		System.out.println("Is Alive" + " " + t1.isAlive());
		t1.start();
		System.out.println("Is Alive" + " " + t1.isAlive());
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("Is Alive" + " " + t1.isAlive());
	}

}

//執行結果:
Is Alive false
Is Alive true
THREAD-A-天王蓋地虎  0
THREAD-A-天王蓋地虎  1
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  4
Is Alive false

4.3 強制執行

4.3.1 語法
public final native boolean isAlive()
4.3.2 示例
//原始碼:
//1. Normal
public class ThreadByExtends_201810281950 extends Thread{
	
	public ThreadByExtends_201810281950() {}
	
	public ThreadByExtends_201810281950(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		outputNormal();
//		outputJoin();
	}
	
	private static void outputNormal(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王蓋地虎");
		t1.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("Main" + " " + i);
		}
	}
	
	private static void outputJoin(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王蓋地虎");
		t1.start();
		for (int i = 0; i < 10; i++) {
			if(i == 5){
				try {
					t1.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("Main" + " " + i);	
		}
	}

}

//執行結果:
Main 0
Main 1
Main 2
THREAD-A-天王蓋地虎  0
THREAD-A-天王蓋地虎  1
THREAD-A-天王蓋地虎  2
Main 3
Main 4
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  4
Main 5
THREAD-A-天王蓋地虎  5
THREAD-A-天王蓋地虎  6
THREAD-A-天王蓋地虎  7
Main 6
Main 7
Main 8
THREAD-A-天王蓋地虎  8
Main 9
THREAD-A-天王蓋地虎  9

//原始碼:
//2. Join
public class ThreadByExtends_201810281950 extends Thread{
	
	public ThreadByExtends_201810281950() {}
	
	public ThreadByExtends_201810281950(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
//		outputNormal();
		outputJoin();
	}
	
	private static void outputNormal(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王蓋地虎");
		t1.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("Main" + " " + i);
		}
	}
	
	private static void outputJoin(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王蓋地虎");
		t1.start();
		for (int i = 0; i < 10; i++) {
			if(i == 5){
				try {
					t1.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("Main" + " " + i);	
		}
	}

}

//執行結果:
Main 0
Main 1
THREAD-A-天王蓋地虎  0
THREAD-A-天王蓋地虎  1
Main 2
Main 3
Main 4
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  4
THREAD-A-天王蓋地虎  5
THREAD-A-天王蓋地虎  6
THREAD-A-天王蓋地虎  7
THREAD-A-天王蓋地虎  8
THREAD-A-天王蓋地虎  9
Main 5
Main 6
Main 7
Main 8
Main 9

由上面的示例可知,當執行緒物件呼叫 join 方法之後,其他正在執行的執行緒必須讓出 CPU,等待此執行緒完成之後才可以繼續執行。

4.4 休眠

4.4.1 語法
public static native void sleep(long millis)
4.4.2 示例
//原始碼:
public class ThreadByExtends_201810282125 extends Thread{

	public ThreadByExtends_201810282125() {}
	
	public ThreadByExtends_201810282125(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810282125 t1 = new ThreadByExtends_201810282125("THREAD-A-天王蓋地虎");
		t1.start();
	}	

}

//執行結果:
THREAD-A-天王蓋地虎  0
THREAD-A-天王蓋地虎  1
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  4

從上面的執行結果裡是不是感覺不到執行緒休眠的效果,那就用心去感受吧,少年。

4.5 中斷執行

4.5.1 語法
public void interrupt()
4.5.2 示例
//原始碼:
public class ThreadByExtends_201810282135 extends Thread{

	public ThreadByExtends_201810282135() {}
	
	public ThreadByExtends_201810282135(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		System.out.println(Thread.currentThread().getName() + "  1.開始休眠");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			System.out.println(Thread.currentThread().getName() + "  *.休眠中斷");
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "  2.休眠結束");
	}

	public static void main(String[] args) {
		ThreadByExtends_201810282135 t1 = new ThreadByExtends_201810282135("THREAD-A-天王蓋地虎");
		t1.start();
		try {
			Thread.sleep(200);
			t1.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

//執行結果:
THREAD-A-天王蓋地虎  1.開始休眠
THREAD-A-天王蓋地虎  *.休眠中斷
java.lang.InterruptedException: sleep interrupted
THREAD-A-天王蓋地虎  2.休眠結束
	at java.lang.Thread.sleep(Native Method)
	at com.smart.www.a_thread_and_runnable.ThreadByExtends_201810282135.run(ThreadByExtends_201810282135.java:31)

4.6 後臺執行

4.6.1 語法
public final void setDaemon(boolean on)
4.6.2 示例
//原始碼:
public class ThreadByExtends_201810282143 extends Thread{

	public ThreadByExtends_201810282143() {}
	
	public ThreadByExtends_201810282143(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		while (true) {
			System.out.println(Thread.currentThread().getName());
		}
	}

	public static void main(String[] args) {
		ThreadByExtends_201810282143 t1 = new ThreadByExtends_201810282143("THREAD-A-天王蓋地虎");
		t1.setDaemon(true);
		t1.start();
		try {
			Thread.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(t1.getState());
	}

}

//執行結果:
…
THREAD-A-天王蓋地虎
THREAD-A-天王蓋地虎
RUNNABLE
THREAD-A-天王蓋地虎
THREAD-A-天王蓋地虎
…

由上面的示例可知,執行緒主體裡面的執行的是死迴圈,因此,正常情況下,Eclipse 的 Console 會一直輸出執行緒的名字。但由於在啟動執行緒之前呼叫了 setDaemon 方法,所以,雖然執行緒的主體是一個死迴圈,但 Java 程序依然可以結束。

下圖就是上面示例控制檯輸出的資料,不難看出,此時的 Java 程序已經結束,但從日誌輸出中可以看出,此時的執行緒處於 RUNNABLE 狀態,即:雖然程序結束了,但後臺執行緒仍然在執行。

4.7 優先順序

4.7.1 語法
public final void setPriority(int newPriority)
4.7.2 示例
//原始碼:
public class ThreadByExtends_201810292043 extends Thread {

	public ThreadByExtends_201810292043() {}
	
	public ThreadByExtends_201810292043(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810292043 t1 = new ThreadByExtends_201810292043("THREAD-A-天王蓋地虎");
		ThreadByExtends_201810292043 t2 = new ThreadByExtends_201810292043("THREAD-B-寶塔鎮河妖");
		ThreadByExtends_201810292043 t3 = new ThreadByExtends_201810292043("THREAD-C-小雞燉蘑菇");
		t1.setPriority(MIN_PRIORITY);
		t2.setPriority(NORM_PRIORITY);
		t3.setPriority(MAX_PRIORITY);
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-B-寶塔鎮河妖  0
THREAD-B-寶塔鎮河妖  1
THREAD-B-寶塔鎮河妖  2
THREAD-C-小雞燉蘑菇  0
THREAD-C-小雞燉蘑菇  1
THREAD-C-小雞燉蘑菇  2
THREAD-A-天王蓋地虎  0
THREAD-A-天王蓋地虎  1
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  3
THREAD-C-小雞燉蘑菇  3
THREAD-B-寶塔鎮河妖  3
THREAD-B-寶塔鎮河妖  4
THREAD-C-小雞燉蘑菇  4
THREAD-A-天王蓋地虎  4

由上面的示例可知,雖然執行緒的給各執行緒都設定了優先順序,但並不代表優先順序高就一定先執行,最終決定哪個執行緒先執行的還是 CPU。

4.8 禮讓

4.8.1 語法
public static native void yield()
4.8.2 示例
//原始碼:
public class ThreadByExtends_201810292053 extends Thread {

	public ThreadByExtends_201810292053() {}
	
	public ThreadByExtends_201810292053(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
			if(i == 2){
				System.out.println(Thread.currentThread().getName() + "  " + "執行緒禮讓");
				Thread.currentThread().yield();
			}
		}
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810292053 t1 = new ThreadByExtends_201810292053("THREAD-A-天王蓋地虎");
		ThreadByExtends_201810292053 t2 = new ThreadByExtends_201810292053("THREAD-B-寶塔鎮河妖");
		ThreadByExtends_201810292053 t3 = new ThreadByExtends_201810292053("THREAD-C-小雞燉蘑菇");
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-A-天王蓋地虎  0
THREAD-B-寶塔鎮河妖  0
THREAD-B-寶塔鎮河妖  1
THREAD-C-小雞燉蘑菇  0
THREAD-C-小雞燉蘑菇  1
THREAD-B-寶塔鎮河妖  2
THREAD-A-天王蓋地虎  1
THREAD-B-寶塔鎮河妖  執行緒禮讓
THREAD-C-小雞燉蘑菇  2
THREAD-C-小雞燉蘑菇  執行緒禮讓
THREAD-B-寶塔鎮河妖  3
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  執行緒禮讓
THREAD-B-寶塔鎮河妖  4
THREAD-C-小雞燉蘑菇  3
THREAD-A-天王蓋地虎  3
THREAD-C-小雞燉蘑菇  4
THREAD-A-天王蓋地虎  4

由上面的示例可知,每當執行緒主體滿足條件(i == 2)時,都會讓出 CPU,讓其他執行緒先執行,然後直接進入執行緒佇列等待排程。

4.9 相關問題

4.9.1 執行緒自動命名是如何實現的?

建立執行緒物件的時候,如果不在構造方法裡面傳執行緒名,那麼系統將會自動為其命名,如:

//原始碼:
public class ThreadByExtends_201810292216 extends Thread {

	public ThreadByExtends_201810292216() {}
	
	public ThreadByExtends_201810292216(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810292216 t1 = new ThreadByExtends_201810292216("THREAD-A-天王蓋地虎");
		ThreadByExtends_201810292216 t2 = new ThreadByExtends_201810292216();
		ThreadByExtends_201810292216 t3 = new ThreadByExtends_201810292216();
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-A-天王蓋地虎
Thread-1
Thread-0

一起看下 Thread 構造方法的定義:

//原始碼:
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

Thread 構造方法中呼叫了 nextThreadNum 方法,nextThreadNum 定義如下:

//原始碼:
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

每次,當構造方法中沒有傳入執行緒名的時候,threadInitNumber 都會 +1,因此最終實現了上面例子所示的結果。

4.9.2 子執行緒可以比主執行緒晚消失嗎?

可以,如:

//原始碼:
public class ThreadByExtends_201810292234 extends Thread {
	
	public ThreadByExtends_201810292234() {}
	
	public ThreadByExtends_201810292234(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		ThreadByExtends_201810292234 t1 = new ThreadByExtends_201810292234("THREAD-A-天王蓋地虎");
		t1.start();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

}

//執行結果:
main  0
THREAD-A-天王蓋地虎  0
main  1
main  2
main  3
main  4
THREAD-A-天王蓋地虎  1
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  4
4.9.3 多個執行緒同時強制執行(join)的時候,哪個先執行?

主執行緒中,有多個執行緒同時強制執行時,此時主執行緒會讓出 CPU,直至強制執行的執行緒執行完才開始繼續執行。強制執行執行緒的執行順序並沒有“先來後到”之分,都是由 CPU 決定的,CPU 想調哪個就執行哪個,即:此時的執行緒和普通的執行緒沒有任何區別。

//原始碼:
public class ThreadByExtends_201810281950 extends Thread{
	
	public ThreadByExtends_201810281950() {}
	
	public ThreadByExtends_201810281950(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		outputJoinMulti1();
	}
	private static void outputJoinMulti1(){
		ThreadByExtends_201810281950 t1 = new ThreadByExtends_201810281950("THREAD-A-天王蓋地虎");
		ThreadByExtends_201810281950 t2 = new ThreadByExtends_201810281950("THREAD-B-寶塔鎮河妖");
		ThreadByExtends_201810281950 t3 = new ThreadByExtends_201810281950("THREAD-C-小雞燉蘑菇");
		t1.start();
		t2.start();
		t3.start();
		for (int i = 0; i < 10; i++) {
			if(i == 5){
				try {
					t1.join();
					t2.join();
					t3.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("Main" + " " + i);	
		}
	}

}

//執行結果:
…
THREAD-B-寶塔鎮河妖  6
Main 3
THREAD-A-天王蓋地虎  2
Main 4
THREAD-B-寶塔鎮河妖  7
THREAD-C-小雞燉蘑菇  3
THREAD-B-寶塔鎮河妖  8
THREAD-C-小雞燉蘑菇  4
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  4
THREAD-C-小雞燉蘑菇  5
THREAD-B-寶塔鎮河妖  9
THREAD-C-小雞燉蘑菇  6
THREAD-A-天王蓋地虎  5
THREAD-C-小雞燉蘑菇  7
THREAD-A-天王蓋地虎  6
THREAD-C-小雞燉蘑菇  8
THREAD-A-天王蓋地虎  7
THREAD-C-小雞燉蘑菇  9
THREAD-A-天王蓋地虎  8
THREAD-A-天王蓋地虎  9
Main 5
Main 6
Main 7
Main 8
Main 9
4.9.4 後臺執行(daemon)的作用?Thread.setDaemon 之後就可以執行完了?

由 《4.6 後臺執行》 的講解可知,執行緒在啟動之前,如果呼叫了 setDaemon 方法,那即使執行緒主體內是死迴圈,此時的 Java 程序也可以結束,但此時的執行緒並沒有結束——依然在執行(RUNNABLE)。

4.9.5 主方法的優先順序
//原始碼:
public class ThreadByExtends_201810292043 extends Thread {

	public ThreadByExtends_201810292043() {}
	
	public ThreadByExtends_201810292043(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
	}
	
	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + "  " + Thread.currentThread().getPriority());
	}

}

//執行結果:
main  5
//原始碼:
/**
 * The minimum priority that a thread can have.
 */
public final static int MIN_PRIORITY = 1;

/**
 * The default priority that is assigned to a thread.
 */
public final static int NORM_PRIORITY = 5;

/**
 * The maximum priority that a thread can have.
 */
public final static int MAX_PRIORITY = 10;

由 Thread 原始碼可知,執行緒優先順序 Priority = 5 對應著 NORM_PRIORITY。

4.9.6 執行緒禮讓,如果是多個執行緒而非兩個,會出現什麼情況?

Thread 類的 yield 方法並不會產生阻塞,只是讓出這一次的 CPU 時間片。讓出 CPU 時間片之後,Thread 物件立刻又到執行緒佇列中排隊等待排程了,而最終等待排程的執行緒哪個先執行,還是由 CPU 決定。因此,在主執行緒中,如果有多個執行緒禮讓,禮讓之後,哪個執行緒先執行,最終由 CPU 決定。

5. 同步與死鎖

5.1 同步

說到執行緒,同步是一個不得不說的話題,為什麼呢?因為多個執行緒訪問同一資源時,如果不進行同步處理,那就會出問題。大家喜聞樂見的當然是賣票問題,如:

//原始碼:
public class ThreadByImplements_201810302136 implements Runnable{

	private int mTicket = 5;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			if(mTicket > 0){
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "  " + (mTicket--));
			}
		}
	}
	
	public static void main(String[] args) {
		ThreadByImplements_201810302136 r1 = new ThreadByImplements_201810302136();
		Thread t1 = new Thread(r1,"THREAD-A-天王蓋地虎");
		Thread t2 = new Thread(r1,"THREAD-B-寶塔鎮河妖");
		Thread t3 = new Thread(r1,"THREAD-C-小雞燉蘑菇");
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-A-天王蓋地虎  3
THREAD-C-小雞燉蘑菇  5
THREAD-B-寶塔鎮河妖  4
THREAD-B-寶塔鎮河妖  2
THREAD-A-天王蓋地虎  2
THREAD-C-小雞燉蘑菇  1
THREAD-B-寶塔鎮河妖  0
THREAD-A-天王蓋地虎  -1

其實簡單分析一下就能明白為什麼會出現上面的執行結果。線上程主體中,判斷票數是否大於零,如果票數大於零,則賣,反之,則不賣。這樣就會出現一種情況:票數為 1 的時候,一個執行緒剛把票買走,系統還沒有來得及減,另外一個執行緒又進來了,由於系統此時還沒有來得及減,所以此時票數還是大於零,結果後來的執行緒也買到票了。

要解決上面的問題,必須使用同步。所謂同步是指在同一時間段內,只能有一個執行緒對資料進行操作,其他執行緒只能等這個執行緒操作完之後才可以操作。

執行緒同步的方法有兩種:同步程式碼塊和同步方法。接下來,分別用兩種方式對上面的例子進行處理。

5.1.1 同步程式碼塊
5.1.1.1 語法
synchronized (同步物件) {
	//需要同步的程式碼塊
}
5.1.1.2 例項
//原始碼:
public class ThreadByImplements_201810302208 implements Runnable {

	private int mTicket = 5;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			synchronized (this) {
				if(mTicket > 0){
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "  " + (mTicket--));
				}
			}
		}
	}
	
	public static void main(String[] args) {
		ThreadByImplements_201810302208 r1 = new ThreadByImplements_201810302208();
		Thread t1 = new Thread(r1,"THREAD-A-天王蓋地虎");
		Thread t2 = new Thread(r1,"THREAD-B-寶塔鎮河妖");
		Thread t3 = new Thread(r1,"THREAD-C-小雞燉蘑菇");
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-A-天王蓋地虎  5
THREAD-A-天王蓋地虎  4
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  1
5.1.2 同步方法
5.1.2.1 語法
synchronized 方法返回值 方法名(引數列表){}
5.1.2.2 例項
//原始碼:
public class ThreadByImplements_201810302220 implements Runnable {

	private int mTicket = 5;
	
	@Override
	public void run() {
		for (int i = 0; i < 25; i++) {
			saleTicket();
		}
	}
	
	private synchronized void saleTicket(){
		if(mTicket > 0){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "  " + (mTicket--));
		}
	}
	
	public static void main(String[] args) {
		ThreadByImplements_201810302220 r1 = new ThreadByImplements_201810302220();
		Thread t1 = new Thread(r1,"THREAD-A-天王蓋地虎");
		Thread t2 = new Thread(r1,"THREAD-B-寶塔鎮河妖");
		Thread t3 = new Thread(r1,"THREAD-C-小雞燉蘑菇");
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-A-天王蓋地虎  5
THREAD-A-天王蓋地虎  4
THREAD-A-天王蓋地虎  3
THREAD-A-天王蓋地虎  2
THREAD-A-天王蓋地虎  1

5.2 死鎖

同步解決了資源共享時資料錯亂的問題,但並不代表同步處理越多越好。例如,週五的時候,你和女票在家做了一頓飯。吃完飯,女票讓你刷鍋,你讓女票打掃衛生。小仙女可不想打掃衛生,於是說:“等你刷完鍋,我就開始打掃衛生”。你一眼識破了女票詭計,於是也說了句:“等你掃完地,我就開始刷鍋”。就這樣,到了第二天,你們的鍋還沒有刷,衛生還沒有打掃,這實際上就是死鎖的概念。

所謂死鎖是指兩個執行緒在等待對方先執行完,造成了程式的停滯。接下來就根據上面例子的示範一下,死鎖是如何出現的:

//原始碼:
public class ThreadByImplements_201810302344 implements Runnable {

	//由於是兩個 Runnable 物件,所以必須使用靜態成員變數才能實現資源共享
	private static GirlFriend gf = new GirlFriend();
	private static BoyFriend bf = new BoyFriend();
	private boolean speakFirst = false;
	
	@Override
	public void run() {
		if(speakFirst){
			synchronized (gf) {
				//1.先下手為強
				gf.say();
				try {
					//2.等待對方刷碗
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (bf) {
					//3.對方刷完碗,自己開始打掃衛生
					gf.clean();
				}
			}
		}else{
			synchronized (bf) {
				//a.以其人之道,還治其人之身
				bf.say();
				try {
					//b.等待對方打掃衛生
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (gf) {
					//c.對方打掃完衛生,自己開始刷鍋
					bf.wash();
				}
			}
		}
	}

	public static void main(String[] args) {
		ThreadByImplements_201810302344 gfRunnable = new ThreadByImplements_201810302344();
		ThreadByImplements_201810302344 bfRunnable = new ThreadByImplements_201810302344();
		gfRunnable.speakFirst = true;
		bfRunnable.speakFirst = false;
		Thread gfThread = new Thread(gfRunnable);
		Thread bfThread = new Thread(bfRunnable);
		gfThread.start();
		bfThread.start();
	}

}

class GirlFriend {
	
	public void say(){
		System.out.println("女生對男生說:你先刷鍋,我再打掃衛生");
	}
	
	public void clean(){
		System.out.println("女生開始打掃衛生");
	}
	
}

class BoyFriend {
	
	public void say(){
		System.out.println("男生對女生說:你先打掃衛生,我再刷鍋");
	}
	
	public void wash(){
		System.out.println("男生開始刷鍋");
	}
	
}

//執行結果:
女生對男生說:你先刷鍋,我再打掃衛生
男生對女生說:你先打掃衛生,我再刷鍋
//後面程式碼不再執行,程式進入死鎖狀態

程式最終執行效果就是下面這個樣子:

5.3 相關問題

5.3.1 兩種建立執行緒的方式均能實現資源共享嗎?如果能,怎麼做?如果不能,為什麼?
5.3.1.1 繼承 Thread 類

通過繼承 Thread 實現執行緒時,Thread 子類中定義的普通變數不可共享,靜態變數可共享,但此時存在“資源共享,但不同步”問題。

接下來,對以下幾種情況進行詳細分析:

序號 處理方式
1 定義普通變數
2 定義普通變數,並通過 synchronized 關鍵字同步當前物件
3 定義靜態變數
4 定義靜態變數,並通過 synchronized 關鍵字同步當前物件

5.3.1.1.1 定義普通變數

//原始碼:
public class ThreadByExtend_201809072104 extends Thread {

	private int bandit = 5;
	
	public ThreadByExtend_201809072104() {}
	
	public ThreadByExtend_201809072104(String n) {
		super(n);
	}
	
	@Override
	public void run() {
		
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  殺了  " + (bandit--) + "  土匪");
			}
		}
		
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809072104 t1 = new ThreadByExtend_201809072104("THREAD-張飛");
		ThreadByExtend_201809072104 t2 = new ThreadByExtend_201809072104("THREAD-趙雲");
		ThreadByExtend_201809072104 t3 = new ThreadByExtend_201809072104("THREAD-呂布");
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-張飛  殺了  5  土匪
THREAD-張飛  殺了  4  土匪
THREAD-張飛  殺了  3  土匪
THREAD-張飛  殺了  2  土匪
THREAD-張飛  殺了  1  土匪
THREAD-呂布  殺了  5  土匪
THREAD-趙雲  殺了  5  土匪
THREAD-趙雲  殺了  4  土匪
THREAD-趙雲  殺了  3  土匪
THREAD-趙雲  殺了  2  土匪
THREAD-呂布  殺了  4  土匪
THREAD-趙雲  殺了  1  土匪
THREAD-呂布  殺了  3  土匪
THREAD-呂布  殺了  2  土匪
THREAD-呂布  殺了  1  土匪

//第一種方式:定義普通變數(不做任何處理)
//結論:資源不共享

5.3.1.1.2 定義普通變數,並通過 synchronized 關鍵字同步當前物件

//原始碼:
public class ThreadByExtend_201809072104 extends Thread {

	private int bandit = 5;
	
	public ThreadByExtend_201809072104() {}
	
	public ThreadByExtend_201809072104(String n) {
		super(n);
	}
	
	@Override
	public void run() {
	
		for (int i = 0; i < 25; i++) {
			synchronized (this) {
				if (bandit > 0) {
					System.out.println(Thread.currentThread().getName() + "  殺了  " + (bandit--) + "  土匪  " + this.hashCode());
				}
			}
		}
		
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809072104 t1 = new ThreadByExtend_201809072104("THREAD-張飛");
		ThreadByExtend_201809072104 t2 = new ThreadByExtend_201809072104("THREAD-趙雲");
		ThreadByExtend_201809072104 t3 = new ThreadByExtend_201809072104("THREAD-呂布");
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-張飛  殺了  5  土匪  1584869782
THREAD-張飛  殺了  4  土匪  1584869782
THREAD-張飛  殺了  3  土匪  1584869782
THREAD-張飛  殺了  2  土匪  1584869782
THREAD-趙雲  殺了  5  土匪  1751905002
THREAD-趙雲  殺了  4  土匪  1751905002
THREAD-趙雲  殺了  3  土匪  1751905002
THREAD-呂布  殺了  5  土匪  736376815
THREAD-呂布  殺了  4  土匪  736376815
THREAD-呂布  殺了  3  土匪  736376815
THREAD-趙雲  殺了  2  土匪  1751905002
THREAD-張飛  殺了  1  土匪  1584869782
THREAD-趙雲  殺了  1  土匪  1751905002
THREAD-呂布  殺了  2  土匪  736376815
THREAD-呂布  殺了  1  土匪  736376815

//第二種方式:定義普通變數,並通過 synchronized 關鍵字同步當前物件
//結論:資源不共享
//synchronized 關鍵字的主要作用是同步,而同步的前提是資源共享,所以,此處的同步處理完全是沒必要的

5.3.1.1.3 定義靜態變數

//原始碼:
public class ThreadByExtend_201809072104 extends Thread {

	private static int bandit = 5;
	
	public ThreadByExtend_201809072104() {}
	
	public ThreadByExtend_201809072104(String n) {
		super(n);
	}
	
	@Override
	public void run() {
	
		for (int i = 0; i < 25; i++) {
			if (bandit > 0) {
				System.out.println(Thread.currentThread().getName() + "  殺了  " + (bandit--) + "  土匪  " + this.hashCode());
			}
		}
		
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809072104 t1 = new ThreadByExtend_201809072104("THREAD-張飛");
		ThreadByExtend_201809072104 t2 = new ThreadByExtend_201809072104("THREAD-趙雲");
		ThreadByExtend_201809072104 t3 = new ThreadByExtend_201809072104("THREAD-呂布");
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-趙雲  殺了  5  土匪  576357660
THREAD-趙雲  殺了  2  土匪  576357660
THREAD-趙雲  殺了  1  土匪  576357660
THREAD-呂布  殺了  3  土匪  1584869782
THREAD-張飛  殺了  4  土匪  366287508

//第三種方式:定義靜態變數
//結論:達到資源共享的目的,但存在“資源共享,但不同步”問題

5.3.1.1.4 定義靜態變數,並通過 synchronized 關鍵字同步當前物件

//原始碼:
public class ThreadByExtend_201809072104 extends Thread {

	private static int bandit = 5;
	
	public ThreadByExtend_201809072104() {}
	
	public ThreadByExtend_201809072104(String n) {
		super(n);
	}
	
	@Override
	public void run() {
	
		for (int i = 0; i < 25; i++) {
			synchronized (this) {
				if (bandit > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "  殺了  " + (bandit--) + "  土匪  " + this.hashCode());
				}
			}
		}
		
	}
	
	public static void main(String[] args) {
		ThreadByExtend_201809072104 t1 = new ThreadByExtend_201809072104("THREAD-張飛");
		ThreadByExtend_201809072104 t2 = new ThreadByExtend_201809072104("THREAD-趙雲");
		ThreadByExtend_201809072104 t3 = new ThreadByExtend_201809072104("THREAD-呂布");
		t1.start();
		t2.start();
		t3.start();
	}

}

//執行結果:
THREAD-呂布  殺了  5  土匪  1584869782
THREAD-趙雲  殺了  4  土匪  576357660
THREAD-張飛  殺了  5  土匪  366287508
THREAD-趙雲  殺了  3  土匪  576357660
THREAD-呂布  殺了  3  土匪  1584869782
THREAD-張飛  殺了  2  土匪  366287508
THREAD-趙雲  殺了  1  土匪  576357660
THREAD-呂布  殺了  -1  土匪  1584869782
THREAD-張飛  殺了  0  土匪  366287508

//第四種方式:定義靜態變數,並通過 synchronized 關鍵字同步當前物件(驗證第三種方式提到的問題)
//結論:未解決“資源共享,但不同步”問題
5.3.1.2 實現 Runnable 介面

通過實現 Runnable 介面實現執行緒時,可以方便地實現資源共享,但此時的“資源共享、同步”存在傾斜性。

接下來,對以下幾種情況進行詳細分析:

序號 處理方式
1 定義普通變數,只建立一個 Runnable 實現類,不同的 Thread 使用同一個 Runnable 實現類(驗證資源共享)
2 定義普通變數,建立三個 Runnable 實現類,不同的 Thread 使用不同的 Runnable 實現類(驗證資源共享)
3 定義普通變數,只建立一個 Runnable 實現類,不同的 Thread 使用同一個 Runnable 實現類(驗證資源同步)
4 定義普通變數,只建立一個 Runnable 實現類,不同的 Thread 使用同一個 Runnable 實現類(驗證 Synchronized 關鍵字)
5 定義靜態變數,只建立一個 Runnable 實現類,不同的 Thread 使用同一個 Runnable 實現類(驗證資源共享)
6 定義靜態變數,建立三個 Runnable 實現類,不同的 Thread 使用不同的 Runnable 實現類(驗證資源共享)
7 定義靜態變數,