多線程編程——實戰篇(三)(轉載)

[深入瞭解線程對象與線程,線程與運行環境]

在基礎篇中的第一節,我就強調過,要了解多線程編程,首要的兩個概念就是線程對象和線程。現在我們來深入理解線程對象,線程,運行環境之間的關係,弄清Runnable與Thread的作用。

  在JAVA平臺中,序列化機制是一個非常重要的機制,如果不能理解並熟練應用序列化機制,你就不能稱得上一個java程序員。

  在JAVA平臺中,爲什麼有些對象中可序列化的,而有些對象就不能序列化?

  能序列化的對象,簡單說是一種可以複製(意味着可以按一定機制進行重構它)的對象,這種對象說到底就是內存中一些數據的組合。只要按一定位置和順序組合就能完整反映這個對象。

  而有些對象,是和當前環境相關的,它反映了當前運行的環境和時序,所以不能被序列,否則在另外的環境和時序中就無法“還原”。

  比如,一個Socket對象:

Socket sc = new Socket("111.111.111.111",80);

  這個sc對象表示當前正在運行這段代碼的主機和IP爲"111.111.111.111"的80端口之間建立的一個物理連結,如果它被序列化,那麼在另一個時刻在另一個主機上它如何能被還原?Socket連結一旦斷開,就已經不存在,它不可能在另一個時間被另一個主機所重現。重現的已經不是原來那個sc對象了。

  線程對象也是這種不可序列化對象,當我們new Thread時,已經初始化了當前這個線程對象所在有主機的運行環境相關的信息,線程調度機制,安全機制等只特定於當前運行環境的信息,假如它被序列化,在另一個環境中運行的時候原來初始化的運行環境的信息就不可能在新的環境中運行。而假如要重新初始化,那它已經不是原來那個線程對象了。

正如Socket封裝了兩個主機之間的連結,但它們並不是已經連結關傳送數據了。要想傳送數據,你還要getInputStream和getOutputStream,並read和write,兩臺主機之間纔開始真正的“數據連結”。

一個Thread對象並建立後,只是有了可以"運行"的令牌,僅僅只是一個"線程對象"。只有當它調用start()後,當前環境纔會分配給它一個運行的 "空間",讓這段代碼開始運行。這個運行的"空間",才叫真正的"線程"。也就是說,真正的線程是指當前正在執行的那一個"事件"。是那個線程對象所在的運行環境。

  明白了上面的概念,我們再來看看JAVA中爲什麼要有Runnable對象和Thread對象。

一、從設計技巧上說,JAVA中爲了實現回調,無法調用方法指針,那麼利用接口來約束實現者強制提供匹配的方法,並將實現該接口的類的實例作爲參數來提供給調用者,這是JAVA平臺實現回調的重要手段。

二、但是從實際的操作來看,對於算法和數據,是不依賴於任何環境的。所以把想要實現的操作中的算法和數據封裝到一個run方法中(由於算法本身是數據的一個部分,所以我把它們合併稱爲數據),可以將離數據和環境的邏輯分離開來。使程序員只關心如何實現我想做的操作,而不要關心它所在的環境。當真正的需要運行的時候再將這段"操作"傳給一個具體當前環境的Thread對象。

三、這是最最重要的原因:實現數據共享

因爲一個線程對象不對多次運行。所以把數據放在Thread對象中,不會被多個線程同時訪問。簡單說:

    class T extends Thread{
Object x;
public void run(){//......;}
}
T t = new T();

當T的實例t運行後,t所包含的數據x只能被一個t.start();對象共享,除非聲明成    static Object x;

  一個t的實例數據只能被一個線程訪問。意思是"一個數據實例對應一個線程"。

  而假如我們從外部傳入數據,比如

  class T extends Thread{
private Object x;
public T(Object x){
this.x = x;
}
public void run(){//......;}
}

  這樣我們就可以先生成一個x對象傳給多個Thread對象,多個線程共同操作一個數據。也就是"一個數據實例對應多個線程"。

  現在我們把數據更好地組織一下,把要操作的數據Object x和要進行的操作一個封裝到Runnable的run()方法中,把Runnable實例從外部傳給多個Thread對象。這樣,我們就有了:

  [一個對象的多個線程]

  這是以後我們要介紹的線程池的重要概念。

  轉載自dev2dev網友axman的go deep into java專欄。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章