1. 程式人生 > >java多執行緒Thread與Runnable的區別與使用深入理解

java多執行緒Thread與Runnable的區別與使用深入理解

首先,多執行緒的實現方式兩種:一種是繼承Thread類,另一種是實現Runnable介面。


那麼這兩種方法的區別何在?該如何選擇?

第一:他們之間的關係

檢視J2EE的API看到

Thread類中:  public class Thread extends Object implements Runnable

明顯可知兩者:Thread類是Runnable介面的一個實現類,那麼Runnable介面是何用?


文件解釋:

Runnable 介面應該由那些打算通過某一執行緒執行其例項的類來實現。類必須定義一個稱為 run 的無引數方法。

設計該介面的目的是為希望在活動時執行程式碼的物件提供一個公共協議。例如,Thread

類實現了 Runnable。啟用的意思是說某個執行緒已啟動並且尚未停止。

也就是說Runnable提供的是一種執行緒執行規範,具體執行執行緒需要通過它的實現類

第二:通過原始碼分析

我們以public class Thread1 extends Thread 這個自定義執行緒來追本溯源:首先檢視Thread類

其中定義了一個private Runnable target;  定義了Runnable型別的屬性target,檢視哪裡有引用

幾個方法:

 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {。。。}

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

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

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

public Thread(String name) {
	init(null, null, name, 0);
    }

 public Thread(ThreadGroup group, String name) {
	init(group, null, name, 0);
    }

public Thread(Runnable target, String name) {
	init(null, target, name, 0);
    }

public Thread(ThreadGroup group, Runnable target, String name) {
	init(group, target, name, 0);
    }

 public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
	init(group, target, name, stackSize);
    }

以上列出了Thread的一個初始化方法init()和所有的構造方法:可以知道,構造方法需要呼叫init()方法初始化執行緒物件,有一個Runnable型別的target物件也參與初始化。

我們所知道的Thread類進行執行執行緒時是呼叫start()方法,我們也來檢視這個方法:

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();
        group.add(this);
        start0();
        if (stopBeforeStart) {
	    stop0(throwableFromStop);
	}
    }

可知:當呼叫這個start()方法時,使該執行緒開始執行;Java 虛擬機器呼叫該執行緒的 run 方法。結果是兩個執行緒併發地執行;當前執行緒(從呼叫返回給 start 方法)和另一個執行緒(執行其 run 方法)。 這個方法僅作了執行緒狀態的判斷(保證一個執行緒不多次啟動,多次啟動在JVM看來這是非法的),然後把該執行緒新增到執行緒組(不多做解釋)等待執行。

那麼繼續看run()方法:

public void run() {
	if (target != null) {
	    target.run();
	}
    }
可知,當Runnable實現類物件沒有內容為null,則方法什麼都不執行,如果有實現類物件,就呼叫它實現類物件實現的run()方法。這讓我們想到了一個經典的設計模式:代理模式

到此我們知道:執行緒的執行是依靠Thread類中的start()方法執行,並且由虛擬機器呼叫run()方法,所以我們必須實現run()方法


那麼還是要看一下Runnable介面的設計:

public
interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used 
     * to create a thread, starting the thread causes the object's 
     * <code>run</code> method to be called in that separately executing 
     * thread. 
     * <p>
     * The general contract of the method <code>run</code> is that it may 
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

可知它只有一個抽象的run()方法,完全是實實在在的執行緒執行規範

第三:通過他們之間的設計模式:代理模式  再次深入

代理模式如圖:

可知,Thread也是Runnable介面的子類,但其沒有完全實現run()方法,所以說如果繼承Thread類實現多執行緒,仍舊需要覆寫run()方法。


看兩種實現多執行緒的基本方式


繼承Thread:

class MyThread extends Thread{
	private int ticket = 5;
	public void run(){
		for(int i = 0;i<100;i++){
			if(ticket>0){//判斷是否還有剩餘票
				System.out.println("賣票,ticket = "+ticket--);
			}
		}
	}
};
public class ThreadDemo04{
	public static void main(String args[]){
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		MyThread mt3 = new MyThread();
		mt1.start();//呼叫執行緒主體讓其執行
		mt2.start();//三個地方同時賣票
		mt3.start();
	}
};	

實現Runnable:
class MyThread implements Runnable{
	private int ticket=5;
	public void run(){
		for(int i = 0;i<100;i++){
			if(ticket>0){
				System.out.println("賣票,ticket = "+ticket--);
			}
		}
	}
};
public class RunnableDemo02{
	public static void main(String args[]){
		MyThread my1 = new MyThread();
		new Thread(my1).start(); //啟動三個執行緒
		new Thread(my1).start(); //共享my1中資源
		new Thread(my1).start(); 
	}
};

可知最後:無論哪種方法都需要實現run()方法,run方法是執行緒的執行主體。並且,執行緒的執行都是呼叫Thread的start()方法。

那麼代理模式中Thread類就充當了代理類,它線上程執行主體執行前作了一些操作然後才執行執行緒的run()。首先說一下代理模式的基本特徵就是對【代理目標進行增強】代理模式就不在這裡詳述。總之,Thread提供了很多有關執行緒執行前、後的操作,然後通過它的start()方法讓JVM自動呼叫目標的run()方法

第四:繼承Thread與實現Runnable介面方法區別

首先看一段程式碼:

new Thread(
    new Runnable(){

              public void run() {
            while(true){
                try {
                        Thread.sleep(500);
                } catch (InterruptedException e) {e.printStackTrace(); }
                    System.out.println("runnable :" + Thread.currentThread().getName());
            }                            
        }
    }
){

        public void run() {
        while(true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {e.printStackTrace();}

                       System.out.println("thread :" + Thread.currentThread().getName());
        }    
    }

}.start();

可以預測一下這段程式碼是執行哪一個run()方法?

根據以前java中基礎知識可知:執行start()方法後,JVM去找run()方法,然後它找到了自己的run()方法,那麼就直接執行自己的run()方法。如果找不到自己的方法它才會去找被代理的run()方法。所以它應該執行的是"thread:。。。"程式碼部分。可以把它放到一個main方法中,通過測試驗證推斷正確:


想說明的是一個面向物件的思想:即如果沒有上方第二個run()塊,那麼它執行的就是匿名Runnable實現類的run()方法。這說明什麼,說明,Thread相當於一個執行者,而執行的程式碼塊在Runnable實現類中定義好。這樣實現執行與原始碼的分離,體現了面向物件的思想。這也是他們之間的一個比較大的區別。

其他區別:

實現Runnable介面可以實現資源共享,Thread無法完成資源共享 ----- 討論第三點的兩段程式碼中:繼承Thread的:結果賣出了15張,各有各的票數。實現Runnable介面的方法:賣出5張,共享了資源

實現Runnable介面比繼承Thread類來實現多執行緒有如下明顯優點:

適合多個相同程式程式碼使用共同資源;

避免由單繼承侷限帶來的影響;

增強程式的健壯性,程式碼能夠被多個執行緒共享,程式碼資料是獨立的;


使用的選擇

通過比對區別可知:

由於面向物件的思想,以及資源共享,程式碼健壯性等,一般都是使用實現Runnable介面來實現多執行緒,也比較推薦

以上為多執行緒的一點個人總結,後期繼續跟上。若有表述不當之處,感謝您的私信指出,我將盡快解決