1. 程式人生 > >Java 學習(五)—— 執行緒

Java 學習(五)—— 執行緒

一、執行緒的基本概念

   

    1.執行緒是一個程式內部的順序控制流。

    2.執行緒和程序的區別 

        ① 每個程序都有獨立的程式碼和資料空間(程序上下文),程序間的切換會消耗比較大的開銷。例如,手機中的app程序;

        ② 執行緒是輕量級的程序,同一類執行緒共享程式碼和資料空間,每個執行緒有獨立的執行棧和程式計數器(PC),執行緒切換開銷小;

        ③ 多程序:作業系統中同時執行多個任務(程式);

        ④ 多執行緒:在同一應用程式中有多個順序流同時執行;

        一個程序包括由作業系統分配的記憶體空間,包含一個或多個執行緒。一個執行緒不能獨立的存在,它必須是程序的一部分。一個程序一直執行,直到所有的非守候執行緒都結束執行後才能結束。

    3.執行緒的生命週期


  • 新建狀態

    : 一個新產生的執行緒從新狀態開始了它的生命週期。它保持這個狀態直到程式start這個執行緒。

  • 執行狀態:當一個新狀態的執行緒被start以後,執行緒就變成可執行狀態,一個執行緒在此狀態下被認為是開始執行其任務

  • 就緒狀態:當一個執行緒等待另外一個執行緒執行一個任務的時候,該執行緒就進入就緒狀態。當另一個執行緒給就緒狀態的執行緒傳送訊號時,該執行緒才重新切換到執行狀態。

  • 休眠狀態: 由於一個執行緒的時間片用完了,該執行緒從執行狀態進入休眠狀態。當時間間隔到期或者等待的事件發生了,該狀態的執行緒切換到執行狀態。

  • 終止狀態: 一個執行狀態的執行緒完成任務或者其他終止條件發生,該執行緒就切換到終止狀態。

    

    4.Java中的執行緒

        ① Java的執行緒通過 java.lang.Thread 類來實現;

        ② VM啟動時,會有一個由主方法(public static void main(){})所定義的執行緒;

        ③ 通過建立 Thread 例項來建立新的執行緒;

        ④ 每個執行緒都是通過某個特定的 Thread 物件所對應的方法 run() 來完成某操作,方法 run () 稱為 執行緒體;

        ⑤ 通過呼叫 Thread 類的 start() 方法來 啟動執行緒;

    執行緒理解:執行緒是一個程式裡面不同的執行路徑;


每一個分支都叫做一個執行緒,main()叫做主分支,也叫主執行緒。

  程只是一個靜態的概念,機器上的一個.class檔案,機器上的一個.exe檔案,這個叫做一個程序。程式的執行過程都是這樣的:首先把程式的程式碼放到記憶體的程式碼區裡面,程式碼放到程式碼區後並沒有馬上開始執行,但這時候說明了一個程序準備開始,程序已經產生了,但還沒有開始執行,這就是程序,所以程序其實是一個靜態的概念,它本身就不能動。平常所說的程序的執行指的是程序裡面主執行緒開始執行了,也就是main()方法開始執行了。程序是一個靜態的概念,在我們機器裡面實際上執行的都是執行緒。

  Windows作業系統是支援多執行緒的,它可以同時執行很多個執行緒,也支援多程序,因此Windows作業系統是支援多執行緒多程序的作業系統。LinuxUinux也是支援多執行緒和多程序的作業系統。DOS就不是支援多執行緒和多程序了,它只支援單程序,在同一個時間點只能有一個程序在執行,這就叫單執行緒

  CPU難道真的很神通廣大,能夠同時執行那麼多程式嗎?不是的,CPU的執行是這樣的:CPU的速度很快,一秒鐘可以算好幾億次,因此CPU把自己的時間分成一個個小時間片,我這個時間片執行你一會,下一個時間片執行他一會,再下一個時間片又執行其他人一會,雖然有幾十個執行緒,但一樣可以在很短的時間內把他們通通都執行一遍,但對我們人來說,CPU的執行速度太快了,因此看起來就像是在同時執行一樣,但實際上在一個時間點上,CPU只有一個執行緒在執行。

學習執行緒首先要理清楚三個概念:

  1. 程序:程序是一個靜態的概念
  2. 執行緒:一個程序裡面有一個主執行緒叫main()方法,是一個程式裡面的,一個程序裡面不同的執行路徑。
  3. 在同一個時間點上,一個CPU只能支援一個執行緒在執行。因為CPU執行的速度很快,因此我們看起來的感覺就像是多執行緒一樣。

  什麼才是真正的多執行緒?如果你的機器是雙CPU,或者是雙核,這確確實實是多執行緒。

二、執行緒的建立和啟動

    Java提供了兩種建立執行緒方法:

  • 通過實現Runnable介面;

  • 通過繼承Thread類本身。

1.通過實現Runnable介面來建立執行緒

① 開闢一個新的執行緒來呼叫run方法

package thread;

public class TestThread1 {

	public static void main(String[] args) {
		Runner1 r1 = new Runner1();		//這裡new了一個執行緒類的物件出來
		//r1.run();				//這個稱為方法呼叫,方法呼叫的執行是等run()方法執行完之後才會繼續執行main()方法
		Thread t = new Thread(r1);		//要啟動一個新的執行緒就必須new一個Thread物件出來
							//這裡使用的是Thread(Runnable target) 這構造方法
		t.start();				//啟動新開闢的執行緒,新執行緒執行的是run()方法,新執行緒與主執行緒會一起並行執行
		for (int i = 0; i < 5; i++) {
			System.out.println("mainthread:"+i);
		}
	}
}

/*定義一個類用來實現Runnable介面,實現Runnable介面就表示這個類是一個執行緒類*/
class Runner1 implements Runnable{
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("Runner1:"+i);
		}
	}
}
    多執行緒程式執行結果:
mainthread:0
Runner1:0
mainthread:1
Runner1:1
mainthread:2
Runner1:2
mainthread:3
Runner1:3
mainthread:4
Runner1:4
     ② 不開闢新執行緒直接呼叫run方法


程式執行結果:

Runner1:0
Runner1:1
Runner1:2
Runner1:3
Runner1:4
mainthread:0
mainthread:1
mainthread:2
mainthread:3
mainthread:4

2.通過實現Thread介面來建立執行緒

    建立一個執行緒的第二種方法是建立一個新的類,該類繼承Thread類,然後建立一個該類的例項。

繼承類必須重寫run()方法,該方法是新執行緒的入口點。它也必須呼叫start()方法才能執行。

package thread;

/**
 * 定義Thread的子類並實現run()方法
 * @author 98736
 *
 */
public class TestThread2 {
	public static void main(String[] args) {
		Runner2 r2 = new Runner2();
		r2.start();			//呼叫start()方法啟動新開闢的執行緒
		for (int i = 0; i < 5; i++) {
			System.out.println("mainMethod:"+i);
		}
	}
}

/*Runner2類從Thread類繼承
通過例項化Runner2類的一個物件就可以開闢一個新的執行緒
呼叫從Thread類繼承來的start()方法就可以啟動新開闢的執行緒*/
class Runner2 extends Thread{
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("Runner2:"+i);
		}
	}	
}

三、執行緒控制的基本方法

方法 描述
public void start() 使該執行緒開始執行;Java 虛擬機器呼叫該執行緒的 run 方法。
public void run() 如果該執行緒是使用獨立的 Runnable 執行物件構造的,則呼叫該 Runnable 物件的 run 方法;否則,該方法不執行任何操作並返回。
public final void setName(String name) 改變執行緒名稱,使之與引數 name 相同。
public final void setPriority(int priority) 更改執行緒的優先順序。
public final void setDaemon(boolean on) 將該執行緒標記為守護執行緒或使用者執行緒。
public final void join(long millisec) 等待該執行緒終止的時間最長為 millis 毫秒。
public final boolean isAlive() 測試執行緒是否處於活動狀態。

測試執行緒是否處於活動狀態。 上述方法是被Thread物件呼叫的。下面的方法是Thread類的靜態方法

方法 描述
public static void yield() 暫停當前正在執行的執行緒物件,並執行其他執行緒。
public static void sleep(long millisec) 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。
public static boolean  holdsLock(Object x) 當且僅當當前執行緒在指定的物件上保持監視器鎖時,才返回 true。
public static Thread currentThread() 返回對當前正在執行的執行緒物件的引用。
public static void dumpStack() 將當前執行緒的堆疊跟蹤列印至標準錯誤流。

四、sleep、join、yield 方法介紹

① sleep方法的應用範例:

package thread;

import java.util.*;

public class TestThreadSleep {
	public static void main(String args[]) {
		MyThread thread = new MyThread();
		thread.start();// 呼叫start()方法啟動新開闢的執行緒
		try {
			/*
			 * Thread.sleep(10000);
			 * sleep()方法是在Thread類裡面宣告的一個靜態方法,因此可以使用Thread.sleep()的格式進行呼叫
			 */
			/*
			 * MyThread.sleep(10000);
			 * MyThread類繼承了Thread類,自然也繼承了sleep()方法,所以也可以使用MyThread.sleep()的格式進行呼叫
			 */
			/*
			 * 靜態方法的呼叫可以直接使用“類名.靜態方法名” 或者“物件的引用.靜態方法名”的方式來呼叫
			 */
			MyThread.sleep(10000);
			System.out.println("主執行緒睡眠了10秒種後再次啟動了");
			// 在main()方法裡面呼叫另外一個類的靜態方法時,需要使用“靜態方法所在的類.靜態方法名”這種方式來呼叫
			/*
			 * 所以這裡是讓主執行緒睡眠10秒種 在哪個執行緒裡面呼叫了sleep()方法就讓哪個執行緒睡眠,所以現在是主執行緒睡眠了。
			 */
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// thread.interrupt();//使用interrupt()方法去結束掉一個執行緒的執行並不是一個很好的做法
		thread.flag = false;// 改變迴圈條件,結束死迴圈
		/**
		 * 當發生InterruptedException時,直接把迴圈的條件設定為false即可退出死迴圈,
		 * 繼而結束掉子執行緒的執行,這是一種比較好的結束子執行緒的做法
		 */
		/**
		 * 呼叫interrupt()方法把正在執行的執行緒打斷 相當於是主執行緒一盆涼水潑上去把正在執行分執行緒打斷了
		 * 分執行緒被打斷之後就會拋InterruptedException異常,這樣就會執行return語句返回,結束掉執行緒的執行
		 * 所以這裡的分執行緒在執行完10秒鐘之後就結束掉了執行緒的執行
		 */
	}
}

class MyThread extends Thread {
	boolean flag = true;// 定義一個標記,用來控制迴圈的條件

	public void run() {
		/*
		 * 注意:這裡不能在run()方法的後面直接寫throw Exception來拋異常,
		 * 因為現在是要重寫從Thread類繼承而來的run()方法,重寫方法不能丟擲比被重寫的方法的不同的異常。 所以這裡只能寫try……catch()來捕獲異常
		 */
		while (flag) {
			System.out.println("==========" + new Date().toLocaleString() + "===========");
			try {
				/*
				 * 靜態方法的呼叫格式一般為“類名.方法名”的格式去呼叫 在本類中宣告的靜態方法時呼叫時直接寫靜態方法名即可。
				 * 當然使用“類名.方法名”的格式去呼叫也是沒有錯的
				 */
				// MyThread.sleep(1000);//使用“類名.方法名”的格式去呼叫屬於本類的靜態方法
				sleep(1000);// 睡眠的時如果被打斷就會丟擲InterruptedException異常
				// 這裡是讓這個新開闢的執行緒每隔一秒睡眠一次,然後睡眠一秒鐘後再次啟動該執行緒
				// 這裡在一個死迴圈裡面每隔一秒啟動一次執行緒,每個一秒打印出當前的系統時間
			} catch (InterruptedException e) {
				/*
				 * 睡眠的時一盤冷水潑過來就有可能會打斷睡眠
				 * 因此讓正在執行執行緒被一些意外的原因中斷的時候有可能會拋被打擾中斷(InterruptedException)的異常
				 */
				return;
				// 執行緒被中斷後就返回,相當於是結束執行緒
			}
		}
	}
}
程式執行結果:
==========2018-5-25 17:34:34===========
==========2018-5-25 17:34:35===========
==========2018-5-25 17:34:36===========
==========2018-5-25 17:34:37===========
==========2018-5-25 17:34:38===========
==========2018-5-25 17:34:39===========
==========2018-5-25 17:34:40===========
==========2018-5-25 17:34:41===========
==========2018-5-25 17:34:42===========
==========2018-5-25 17:34:43===========
主執行緒睡眠了10秒種後再次啟動了
② join方法的應用範例:
package thread;

public class TestThreadJoin {
	public static void main(String args[]) {
        MyThread2 thread2 = new MyThread2("mythread");
        // 在建立一個新的執行緒物件的同時給這個執行緒物件命名為mythread
        thread2.start();// 啟動執行緒
        try {
            thread2.join();// 呼叫join()方法合併執行緒,將子執行緒mythread合併到主執行緒裡面
            // 合併執行緒後,程式的執行的過程就相當於是方法的呼叫的執行過程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i <= 5; i++) {
            System.out.println("I am main Thread");
        }
    }
}

class MyThread2 extends Thread {
    MyThread2(String s) {
        super(s);
        /*
         * 使用super關鍵字呼叫父類的構造方法 
         * 父類Thread的其中一個構造方法:“public Thread(String name)” 
         * 通過這樣的構造方法可以給新開闢的執行緒命名,便於管理執行緒
         */
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("I am a\t" + getName());
            // 使用父類Thread裡面定義的
            //public final String getName(),Returns this thread's name.
            try {
                sleep(1000);// 讓子執行緒每執行一次就睡眠1秒鐘
            } catch (InterruptedException e) {
                return;
            }
        }
    }
}
程式執行結果:
I am a	mythread
I am a	mythread
I am a	mythread
I am a	mythread
I am a	mythread
I am main Thread
I am main Thread
I am main Thread
I am main Thread
I am main Thread
I am main Thread
③ yield方法的應用範例:
package thread;

public class TestThreadYield {
	public static void main(String args[]) {
		MyThread3 t1 = new MyThread3("t1");
		/* 同時開闢了兩條子執行緒t1和t2,t1和t2執行的都是run()方法 */
		/* 這個程式的執行過程中總共有3個執行緒在並行執行,分別為子執行緒t1和t2以及主執行緒 */
		MyThread3 t2 = new MyThread3("t2");
		t1.start();// 啟動子執行緒t1
		t2.start();// 啟動子執行緒t2
		for (int i = 0; i <= 5; i++) {
			System.out.println("I am main Thread");
		}
	}
}
class MyThread3 extends Thread {
    MyThread3(String s) {
        super(s);
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(getName() + ":" + i);
            if (i % 2 == 0) {
                yield();// 當執行到i能被2整除時當前執行的執行緒就讓出來讓另一個在執行run()方法的執行緒來優先執行
                /*
                 * 在程式的執行的過程中可以看到,
                 * 執行緒t1執行到(i%2==0)次時就會讓出執行緒讓t2執行緒來優先執行 
                 * 而執行緒t2執行到(i%2==0)次時也會讓出執行緒給t1執行緒優先執行
                 */
            }
        }
    }
}

程式執行結果:

I am main Thread
I am main Thread
I am main Thread
I am main Thread
I am main Thread
I am main Thread
t2:1
t2:2
t1:1
t1:2
t2:3
t1:3
t2:4
t1:4
t2:5
t1:5
文章參考: 孤傲蒼狼