1. 程式人生 > >多執行緒之建立執行緒的兩種方式

多執行緒之建立執行緒的兩種方式

彩蛋:

朕是小白,朕近段時間準備學一下有關執行緒方面的知識。(下面寫的東西是我對執行緒的一些理解)

今天是2018年5月28號,朕準備每天更新一篇部落格來激勵自己一直學習,不要間斷,畢竟是位又懶自制力又差的傢伙,哈哈哈!

言歸正傳,今晚準備詳細記錄一下執行緒建立的兩種方式以及這兩種方式的優缺點及適應的情況等等。

多程序:。。。

多執行緒:在一個應用程式中,有多個順序流同時執行。(執行緒:順序執行的程式碼)

建立執行緒有兩種方式:

一,(目標類來)繼承Thread類,重寫run方法來建立自己的執行緒。呼叫start函式就開始執行執行緒程式碼昂發啦!

從Thread類派生一個子類,並建立子類的物件。

子類應該重寫Thread類的run方法,寫入需要在新執行緒中執行的語句段。

呼叫start方法來啟動新執行緒,自動進入run方法。


舉例:在新執行緒中實現計算某個整數的階乘的過程:

package multi_thread;

public class FactorialThreadTester {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println("main thread starts");
		FactorialThread thread = new FactorialThread(10);
		thread.start();
		System.out.println("main thread ends");
	}
}

class FactorialThread extends Thread{
	private int num;
	public FactorialThread(int num) {
		this.num = num;
	}
	public void run() {
		int i = num;
		int result = 1;
		System.out.println("new thread started");
		while(i>0) {
			result*=i;
			i--;
		}
		System.out.println("The factorial of "+num+" is "+result);
		System.out.println("new thread ends");
	}
}

執行結果:

備註:factorial(階乘)
main thread starts
main thread ends
new thread started
The factorial of 10 is 3628800
new thread ends

注:這裡之所以要另外寫一個主執行緒(public static void main(String[] args))是為了想說明建立的新執行緒並不是start()後就立即執行的。它其實也需要CPU來進行排程,輪到它了它才會被執行。通過執行結果我們可以看出。

通過繼承Thread類來建立執行緒大致流程就是上面這樣。

下面我想補充一些個人感覺很有用的知識(如果急著看另一種建立執行緒的方式,請往下翻~~~):

Thread類常用的API方法,整理如下:

名稱說明
public Thread()構造一個新的執行緒物件(Thread類的建構函式),預設名為Thread-n,n是從0開始遞增的整數
public Thread(Runnable target)構造一個新的執行緒物件,以一個實現Runnable介面的類的物件為引數,預設名為Thread-n,n是從0開始遞增的整數(哈哈哈,從此處建構函式可以看出建立執行緒的兩種不同方式,處處點題,我真機智!)
public Thread(String name)構造一個執行緒物件,並同時指定執行緒名
public static Thread currentThread()返回一個當前正在執行的執行緒物件
public static void yield()使當前執行緒物件暫停,允許別的執行緒開始執行(注:yield:屈服)
public static void sleep(long millis)使當前執行緒暫停執行指定毫秒數,但此執行緒並不失去已獲得的鎖
public void start()啟動執行緒,JVM將呼叫此執行緒的run方法,結果是將同時執行兩個執行緒,當前執行緒和執行run方法的執行緒
public void run()Thread的子類應該重寫此方法,內容應為該執行緒應執行的任務
public final void stop()停止執行緒執行,釋放該執行緒佔用的物件鎖
public void interrupt()中斷此執行緒
public final void join()如果啟動了執行緒A,呼叫join方法將等待執行緒A死亡才能繼續執行當前執行緒
public final void join(long millis)如果此前啟動了執行緒A,呼叫join方法將等待指定毫秒數或執行緒A死亡才能繼續執行當前執行緒
設定執行緒優先順序
public final void setPriority( int newPriority)設定執行緒優先順序
public final void setDaemon(Boolean on)設定是否為後臺執行緒,如果當前執行執行緒均為後臺執行緒則JVM停止執行。這個方法必須在start()方法前使用(注:daemon:後臺程式)
public final void checkAccess()判斷當前執行緒是否有權力修改呼叫此方法的執行緒
public void setName(String name)更改本執行緒的名稱為指定引數
public final boolean isAlive()測試執行緒是否處於活動狀態,如果執行緒被啟動並且沒有死亡則返回true

二,寫一個來實現Runnable介面,在初始化一個Thread類或者Thread子類的執行緒物件的時候 ,把該類的物件作為引數傳遞給那個執行緒物件。(其中由該類提供run方法)。

Runnable介面:只有一個run()方法

以實現Runnable介面的類的物件為引數建立新的執行緒


還是舉這個例子:在新執行緒中實現計算某個整數的階乘的過程:

package multi_thread;

public class FactorialThreadTest_Runnable {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println("main thread starts");
		FactorialThreadRunnable t = new FactorialThreadRunnable(10);
		new Thread(t).start();
		System.out.println("new thread started, main thread ends");
	}
}

class FactorialThreadRunnable implements Runnable{
	private int num;
	public FactorialThreadRunnable(int num) {
		this.num = num;
	}
	public void run() {
		int i = num;
		int result = 1;
		while(i>0) {
			result*=i;
			i--;
		}
		System.out.println("The factorial of "+num+" is "+result);
		System.out.println("new thread ends");
	}
}

執行結果:

main thread starts
new thread started, main thread ends
The factorial of 10 is 3628800
new thread ends


再舉一個使用Runnable介面實現上面多執行緒的例子:

package multi_thread;

public class ThreadSleepTester_Runnable {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		TestThread_Runnable thread1 = new TestThread_Runnable();
		TestThread_Runnable thread2 = new TestThread_Runnable();
		TestThread_Runnable thread3 = new TestThread_Runnable();
		System.out.println("Starting threads");
		new Thread(thread1,"Thread1").start();
		new Thread(thread2,"Thread2").start();
		new Thread(thread3,"Thread3").start();
		System.out.println("Threads started,main ends");
	}

}

class TestThread_Runnable implements Runnable{
	private int sleepTime;
	public TestThread_Runnable() {
		sleepTime=(int)(Math.random()*6000);
	}
	public void run() {
		try {
			System.out.println(Thread.currentThread().getName()+" going to sleep for "+sleepTime);
			Thread.sleep(sleepTime);
		}
		catch(InterruptedException e){};
		System.out.println(Thread.currentThread().getName()+" finished");
	}
}

執行結果:

Starting threads
Threads started,main ends
Thread1 going to sleep for 5924
Thread3 going to sleep for 2385
Thread2 going to sleep for 2112
Thread2 finished
Thread3 finished
Thread1 finished

通過實現Runnable介面來建立執行緒大致流程就是上面這樣。

接下來我們要思考一個問題:建立執行緒的兩種方式各有其優缺點:

直接繼承Thread類:

優點:編寫簡單,直接繼承,重寫run方法。

缺點:由於Java的單繼承機制,不能再繼承其他類了

使用Runnable介面:

優點有兩個:一,可以繼承其他類。Java不支援多繼承,如果已經繼承了某個基類,便需要實現Runnable介面來生成多執行緒。

二,可以將CPU,程式碼和資料分開,形成清晰的模型,便於多個執行緒共享資源。

其他的優缺點應該很好理解,但對於第二個(二,可以將CPU,程式碼和資料分開,形成清晰的模型,便於多個執行緒共享資源)優點,還是舉個例子來說明比較好!

通過實現Runnable介面為啥多個執行緒就可以共享資料等資源呢???

package multi_thread;

public class ShareTargetTester {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		TestThread_Runnable thread = new TestThread_Runnable();
		System.out.println("Starting threads");
		new Thread(thread,"Thread1").start();
		new Thread(thread,"Thread2").start();
		new Thread(thread,"Thread3").start();
		System.out.println("Threads started,main ends");
	}

}
class TestThread_Runnable implements Runnable{
	private int sleepTime;
	public TestThread_Runnable() {
		sleepTime=(int)(Math.random()*6000);
	}
	public void run() {
		try {
			System.out.println(Thread.currentThread().getName()+" going to sleep for "+sleepTime);
			Thread.sleep(sleepTime);
		}
		catch(InterruptedException e){};
		System.out.println(Thread.currentThread().getName()+" finished");
	}
}

執行結果:

Starting threads
Threads started,main ends
Thread1 going to sleep for 4422
Thread2 going to sleep for 4422
Thread3 going to sleep for 4422
Thread2 finished
Thread1 finished
Thread3 finished
Thread1、Thread2和Thread3同時結束。(你執行一下看看就知道啦!)

這裡為啥Thread1、Thread2和Thread3休眠時間是一樣的呢?好奇怪!(emmmm,寡人還是不故弄玄虛了。。。)

原因是:你建立3個新執行緒傳給Thread的引數是一毛一樣滴,所以那3個新執行緒就共享了一個物件裡的資料(sleepTime)啊,所以就是一樣的!

		TestThread_Runnable thread = new TestThread_Runnable();
		new Thread(thread,"Thread1").start();
		new Thread(thread,"Thread2").start();
		new Thread(thread,"Thread3").start();

扶朕起來,朕還可以再舉一個例子:

用三個執行緒模擬三個售票口,總共出售10張票。(注:這三個執行緒應該共享10張票的資料,因為總共就10張票,三個程序一起來銷售)

package multi_thread;

public class sellTicketsTester {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		sellTickets t = new sellTickets();
		new Thread(t,"Thread1").start();
		new Thread(t,"Thread2").start();
		new Thread(t,"Thread3").start();
		
	}
}

class sellTickets implements Runnable{
	private int tickets = 10;
	public void run() {
		while(tickets>0) {
			System.out.println(Thread.currentThread().getName()+" is selling ticket "+tickets--);
		}
	}
}

執行結果:

Thread1 is selling ticket 10
Thread3 is selling ticket 8
Thread2 is selling ticket 9
Thread3 is selling ticket 6
Thread3 is selling ticket 4
Thread3 is selling ticket 3
Thread3 is selling ticket 2
Thread3 is selling ticket 1
Thread1 is selling ticket 7
Thread2 is selling ticket 5

哈哈哈,看完執行結果你就明白我滴意思了吧!

在這個例子中,建立了3 個執行緒,每個執行緒呼叫的是同一個sellTickets物件t中的run()方法,訪問的也是同一個物件t中的變數tickets。

但是,如果是通過建立Thread類的子類來模擬售票過程,再建立3個新執行緒,則每個執行緒都有各自的方法和變數,雖然方法是相同的,但確是各有10張票,結果就會是每個執行緒都賣出了10張票,總共賣出了30張票,與原意就不符了!!!

(個人感覺執行緒間的資料共享挺重要的,也挺有意思的)

我還想補充一下執行緒有關的其他知識:

一、執行緒休眠:(呼叫sleep方法)

比如之前寫的程式:

package multi_thread;

public class FactorialThreadTester {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println("main thread starts");
		FactorialThread thread = new FactorialThread(10);
		thread.start();
		System.out.println("main thread ends");
	}
}

class FactorialThread extends Thread{
	private int num;
	public FactorialThread(int num) {
		this.num = num;
	}
	public void run() {
		int i = num;
		int result = 1;
		System.out.println("new thread started");
		while(i>0) {
			result*=i;
			i--;
		}
		System.out.println("The factorial of "+num+" is "+result);
		System.out.println("new thread ends");
	}
}

執行結果:

備註:factorial(階乘)
main thread starts
main thread ends
new thread started
The factorial of 10 is 3628800
new thread ends

現在我讓主執行緒休眠一毫秒,執行結果就會發生改變了哦,因為主執行緒休眠了一毫秒!

package multi_thread;

public class FactorialThreadTester {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println("main thread starts");
		FactorialThread thread = new FactorialThread(10);
		thread.start();
		try { Thread.sleep(1);}
		catch(Exception e) {};
		System.out.println("main thread ends");
	}
}

class FactorialThread extends Thread{
	private int num;
	public FactorialThread(int num) {
		this.num = num;
	}
	public void run() {
		int i = num;
		int result = 1;
		System.out.println("new thread started");
		while(i>0) {
			result*=i;
			i--;
		}
		System.out.println("The factorial of "+num+" is "+result);
		System.out.println("new thread ends");
	}
}

與上一個程式不同的是我只添加了紅色標記的語句,執行結果見下:

main thread starts
new thread started
The factorial of 10 is 3628800
new thread ends
main thread ends

新執行緒結束後main執行緒才結束。

多個執行緒休眠的情況見下:

package multi_thread;

public class ThreadSleepTester {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		TestThread thread1 = new TestThread("Thread1");
		TestThread thread2 = new TestThread("Thread2");
		TestThread thread3 = new TestThread("Thread3");
		System.out.println("Starting threads");
		thread1.start();
		thread2.start();
		thread3.start();
		System.out.println("Threads started,main ends");
	}
}
	
	class TestThread extends Thread{
		private int sleepTime;
		public TestThread(String name) {
			super(name);
			sleepTime=(int)(Math.random()*6000);
		}
		public void run() {
			try {
				System.out.println(getName()+" going to sleep for "+sleepTime);
				Thread.sleep(sleepTime);
			}
			catch(InterruptedException e){};
			System.out.println(getName()+" finished");
		}
}

備註:Math.random()產生0~1之間的隨機數。

Thread.sleep(sleepTime),其中sleepTime的單位是毫秒!

執行結果見下:

Starting threads
Threads started,main ends
Thread1 going to sleep for 5492
Thread2 going to sleep for 1295
Thread3 going to sleep for 3126
Thread2 finished
Thread3 finished
Thread1 finished

由於執行緒1休眠時間最長,所以最後結束;執行緒2休眠時間最短,所以最先結束。

每次執行,都會隨機產生不同的休眠時間,所以其實每次執行的結果都不相同!

結尾也有彩蛋,嘻嘻:

eclipse變數名如何實現一改全改呢?(個人感覺還是很有用的小技巧)

雙擊選擇要改的變數名,右鍵下拉選單選擇refactor重構選項,選擇rename,然後就輸入自己需要改成的名字,回車,搞定!