深刻理解Thread類

本文經過幾個問題入手,主要經過案例講解Thread類的使用,以及注意事項,最後站在源碼的角度分析問題。java

1.多線程必定快嗎?

多線程不必定快!多線程

多線程只是極大限度的利用CPU的空閒時間來處理其餘的任務,這樣纔會縮短多個任務的執行時間。若是在沒有CPU空閒的狀況下,多線程之間的上下文切換(保存線程的運行環境,還原線程的運行環境)會產生開銷,執行時間會慢於單線程。異步

2.區分單任務操做系統與多任務操做系統

單任務操做系統:同步執行,排隊。只有當前任務執行完了才能夠執行下一個任務。例如cmd中執行完一條指令以後才能夠執行另外一條指令ide

多任務操做系統:異步執行,經過時間片的快速切換實現多個任務響應執行。源碼分析

3.建立Thread類的兩種形式,及其差異

(1)繼承Thread類,重寫run( )方法
測試

package com.feng.example;

public class MyThread extends Thread {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		System.out.println("繼承Thread類");
	}	
}

啓動線程:spa

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyThread myThread = new MyThread();
		myThread.start();

	}

}

(2)建立一個Runnable接口的實現類A,將A的實例對象做爲Thread類的參數傳入操作系統

package com.feng.example;

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("經過實現Runnable接口重寫run");
	}

}

啓動線程:線程

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Runnable myRunnable = new MyRunnable();
		Thread myThread = new Thread(myRunnable);
		myThread.start();

	}

}

因爲在Thread類也是實現Runnable接口的,所以Thread的對象也能夠做爲第二種形式的參數code

public class Thread implements Runnable {} //這裏只是貼出源碼中Thread類的定義

將第一種方式建立的MyThread類對象做爲第二種方式的輸入參數

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//建立MyThread類對象
		Runnable myRunnable = new MyThread();
		Thread myThread = new Thread(myRunnable);
		myThread.start();

	}

}

兩種方式的區別:兩種方式沒有本質的區別,通常都會使用第二種形式,主要解決java中類不能多繼承的缺陷。

好比Cat類繼承Animal,同時又想將Cat定義爲線程類,由於Cat不能同時繼承Animal,Thread類,可是Cat能夠繼承Animal,實現Runnable接口

4.經過線程的start( )方法運行run( )方法中的代碼與直接調用run( )方法的區別

回顧一下操做系統課中線程的建立過程:線程的建立包括兩部分

(1)建立線程,至關於MyThread  myThread = new MyThread( );

(2)將線程放到就緒隊列中,若是僅僅只是建立了線程卻沒有將線程放到就緒隊列中,線程調度器是無法找到線程的。所以myThread.start( );將線程放到就緒隊列中。等待分配cpu時間片,當獲取了cpu以後就能夠執行線程的run( )方法

經過start( )方法:是新開闢了一個線程,run( )方法是由線程規劃器來調用的,是異步運行

直接調用run( )方法:調用者是Thread子類的一個對象,屬於同步執行,沒有開闢新的線程。

package com.feng.example;

public class MyThread extends Thread {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		System.out.println("線程爲:"+Thread.currentThread().getName());
	}	
}
package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//建立MyThread類對象
		MyThread myThread = new MyThread();
		myThread.start();
		
		myThread.run();  //同步調用run方法  

	}

}

程序運行結果:

5.兩種實現方式一塊使用,會調用那種方式的run( )方法,經過源碼分析

首先查看Thread類源碼中是如何重寫Runnable接口中的run( )方法的。

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

接下來看一下這個target又是什麼?

/* What will be run. */
    private Runnable target;

由源碼能夠看出,在Thread類的內部,存在一個類型爲Runnable的成員變量,此成員變量即是接收第二種建立線程類的方法中的輸入參數的。再看Thread類的run方法,只是判斷有此target存不存在,若是存在便執行target的run( )方法。

若是不存在怎麼辦?

若是不存在則是使用繼承的方式來實現的線程類,那麼run( )方法已經在Thread子類中進行了重寫,覆蓋了父類的run( )方法

下面經過一個題目來消化這個知識點:

Runnable接口的實現類以下:

package com.feng.example;

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("經過Runnable接口實現線程類");
	}

}

Thread的子類實現以下:

package com.feng.example;

public class MyThread extends Thread {

	public MyThread(Runnable target)
	{
		super(target);
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("經過繼承實現線程類");
	}	
}

測試代碼以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//建立Runnable接口實現類
		Runnable myRunnable = new MyRunnable();
		//建立MyThread類對象
		MyThread myThread = new MyThread(myRunnable);
		
		//到底輸出的是誰的run方法????
		myThread.start();
		

	}

}

分析: 首先start的是MyThread線程,由於會執行MyThread類的run( )方法。雖然傳入了Runnable類型的參數,可是在MyThread類中的run( )方法中並無super.run( );所以程序只輸出MyThread類中的run( )方法中的輸出。

輸出結果如圖:

修改MyThread類中的run( )方法:

package com.feng.example;

public class MyThread extends Thread {

	public MyThread(Runnable target)
	{
		super(target);
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		System.out.println("經過繼承實現線程類");
	}	
}

分析:

首先start的是MyThread線程,由於會執行MyThread類的run( )方法。由於在構造MyThread時傳入了Runnable類型的參數,所以在執行MyThread類中的run( )方法時,先執行super.run( ); 執行Thread類的run( ); 根據源代碼先檢查target存不存,這裏target就是存入的myRunnable, 因此執行myRunnable裏面的run( );執行完super.run( )方法後,在輸出後面的語句。

輸入結果以下圖:

在有些地方可能會以匿名類的形式來進行考察:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		new Thread(new Runnable(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("Runnable...");
			}
			
		}){
			public void run()
			{
				//super.run();
				System.out.println("Thread...");
			}
			
		}.start();

	}

}

看到此代碼不要驚慌,其實與我最初定義的三個類文件的考察方式是同樣的,只不過這裏使用了匿名類,若是忘記了回去翻看匿名類的知識,此題注意註釋的那一句super.run( ); 看一下注釋不註釋有什麼區別。

只要理解Thread源碼中的run( )方法,這個題目就顯得很簡單了。

相關文章
相關標籤/搜索