並發編程(五) 創建線程的四種方式


線程的創建一共有四種方式:

  • 繼承於Thread類,重寫run()方法;
  • 實現Runable接口,實現里面的run()方法;
  • 使用 FutureTask 實現有返回結果的線程
  • 使用ExecutorServiceExecutors 線程池。

  在詳細了解這四種方法之前,先來理解一下為什么線程要這樣創建:形象點來說,Thread是一個工人,run()方法里面的便是他的任務欄,這個任務欄默認是空的。當你想要這個線程做點什么時,你可以重寫Thread里面的run方法,重寫這個工人的任務欄;也可以通過runable、callable接口,從外部賦予這個工人任務。還可以將任務交給一堆工人,誰有空就誰就承擔這個任務(線程池)。

一、四種方式的詳細介紹

1、繼承於Thread類,重寫run()方法

Thread thread = new MyThread();
	//線程啟動
thread.start();

MyThread 類

//繼承Thread
class MyThread extends Thread{

	//重寫run方法
	@Override
	public void run() {
		//任務內容....
		System.out.println("當前線程是:"+Thread.currentThread().getName());
	}
}

運行結果:
當前線程是:Thread-0

如果線程類使用的很少,那么可以使用匿名內部類,請看下面的例子:

Thread thread = new Thread(){

		@Override
		public void run() {
			//任務內容....
			System.out.println("當前線程是:"+Thread.currentThread().getName());
		}
		
	};

2、實現Runable接口,實現里面的run()方法:

  第一種方法- -繼承Thread類的方法,一般情況下是不建議用的,因為java是單繼承結構,一旦繼承了Thread類,就無法繼承其他類了。所以建議使用 實現Runable接口 的方法;

Thread thread = new Thread(new MyTask());
	//線程啟動
thread.start();

MyTask 類:

//實現Runnable接口
class MyTask implements Runnable{

	//重寫run方法
	public void run() {
		//任務內容....
		System.out.println("當前線程是:"+Thread.currentThread().getName());
	}
}

同樣,如果這個任務類(MyTask )用的很少,也可以使用匿名內部類:

Thread thread = new Thread(new Runnable() {
		
		@Override
		public void run() {
			//任務內容....
			System.out.println("當前線程是:"+Thread.currentThread().getName());
		}
	});

3、使用 FutureTask 實現有返回結果的線程

  FutureTask 是一個可取消的異步計算任務,是一個獨立的類,實現了 Future、Runnable接口。FutureTask 的出現是為了彌補 Thread 的不足而設計的,可以讓程序員跟蹤、獲取任務的執行情況、計算結果
  因為 FutureTask實現了 Runnable,所以 FutureTask 可以作為參數來創建一個新的線程來執行,也可以提交給 Executor 執行。FutureTask 一旦計算完成,就不能再重新開始或取消計算。

FutureTask的構造方法

可以接受 Runnable,Callable 的子類實例。

//創建一個 FutureTask,一旦運行就執行給定的 Callable。
public FutureTask(Callable<V> callable);

//創建一個 FutureTask,一旦運行就執行給定的 Runnable,並安排成功完成時 get 返回給定的結果 。
public FutureTask(Runnable runnable, V result)

@ Example1 FutureTask 的簡單例子

public class Test {
  public static void main(String[] args) throws InterruptedException, ExecutionException {
	FutureTask<Double> task = new FutureTask(new MyCallable());
    //創建一個線程,異步計算結果
	Thread thread = new Thread(task);
	thread.start();
	//主線程繼續工作
	Thread.sleep(1000);
	System.out.println("主線程等待計算結果...");
    //當需要用到異步計算的結果時,阻塞獲取這個結果
	Double d = task.get();
	System.out.println("計算結果是:"+d);
    
    //用同一個 FutureTask 再起一個線程
    Thread thread2 = new Thread(task);
	thread2.start();
}
}

class MyCallable implements Callable<Double>{

	@Override
	public Double call() {
		 double d = 0;
		 try {
			 System.out.println("異步計算開始.......");
			  d = Math.random()*10;
			 d += 1000;
			Thread.sleep(2000);
			 System.out.println("異步計算結束.......");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return d;
	}
}

運行結果:

異步計算開始.......
主線程等待計算結果...
異步計算結束.......
計算結果是:1002.7806590582911

四、使用線程池ExecutorSerice、Executors

  前面三種方法,都是顯式地創建一個線程,可以直接控制線程,如線程的優先級、線程是否是守護線程,線程何時啟動等等。而第四種方法,則是創建一個線程池,池中可以有1個或多個線程,這些線程都是線程池去維護,控制程序員不需要關心這些細節,只需要將任務提交給線程池去處理便可,非常方便。
  創建線程池的前提最好是你的任務量大,因為創建線程池的開銷比創建一個線程大得多。

創建線程池的方式

  ExecutorService 是一個比較重要的接口,實現這個接口的子類有兩個 ThreadPoolExecutor (普通線程池)、ScheduleThreadPoolExecutor (定時任務的線程池)。你可以通過這兩個類來創建一個線程池,但要傳入各種參數,不太方便。
  為了方便用戶,JDK中提供了工具類Executors,提供了幾個創建常用的線程池的工廠方法。由於篇幅原因,不細說,可參考我的並發系列文章。

@ Example2 Executors 創建單線程的線程池

public class MyTest {
	public static void main(String[] args) {
		 //創建一個只有一個線程的線程池
         ExecutorService executorService = Executors.newSingleThreadExecutor();
         //創建任務,並提交任務到線程池中
         executorService.execute(new MyRunable("任務1"));
         executorService.execute(new MyRunable("任務2"));
         executorService.execute(new MyRunable("任務3"));
	}
}

class MyRunable implements Runnable{

	private String taskName;
	
	public MyRunable(String taskName) {
		this.taskName = taskName;
	}

	@Override
	public void run() {
		System.out.println("線程池完成任務:"+taskName);
	}
}

二、關於run()方法的思考

看看下面這種情況:線程類Thread 接收了外部任務,同時又用匿名內部類的方式重寫了內部的run()方法,這樣豈不是有兩個任務,那么究竟會執行那個任務呢?還是兩個任務一起執行呢?

Thread thread = new Thread(new MyTask()){
		@Override
		public void run() {//重寫Thread類的run方法
			System.out.println("Thread 類的run方法");
		}
	};
	//線程啟動
	thread.start();
//實現Runnable接口
class MyTask implements Runnable{

	//重寫run方法
	@Override
	public void run() {
		//任務內容....
		System.out.println("這是Runnable的run方法");
	}
}

運行結果:
Thread 類的run方法

  通過上面的結果,可以看出:線程最后執行的是Thread類內部的run()方法,這是為什么呢?我們先來分析一下JDK的Thread源碼:

private Runnable target;
public void run() {  
    if (target != null) {  
        target.run();  
    }  
}  

  一切都清晰明了了,Thread類的run方法在沒有重寫的情況下,是判斷一下是否有Runnable 對象傳進來,如果有,那么就調用Runnable 對象里的run方法;否則,就什么都不干,線程結束。所以,針對上面的例子,一旦你繼承重寫了Thread類的run()方法,而你又想可以接收Runable類的對象,那么就要加上super.run(),執行沒有重寫時的run方法,改造的例子如下:

Thread thread = new Thread(new MyTask()){
		@Override
		public void run() {//重寫Thread類的run方法
 		   //調用父類Thread的run方法,即沒有重寫時的run方法
	    	super.run();
			System.out.println("Thread 類的run方法");
		}
	};

運行結果:
這是Runnable的run方法
Thread 類的run方法




參考文獻:
http://blog.csdn.net/aboy123/article/details/38307539


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM