天天看點

Java并發程式設計:如何建立線程?

Java并發程式設計:如何建立線程?

  在前面一篇文章中已經講述了在程序和線程的由來,今天就來講一下在Java中如何建立線程,讓線程去執行一個子任務。下面先講述一下Java中的應用程式和程序相關的概念知識,然後再闡述如何建立線程以及如何建立程序。下面是本文的目錄大綱:

  一.Java中關于應用程式和程序相關的概念

  二.Java中如何建立線程

  三.Java中如何建立程序

  若有不正之處,請多多諒解并歡迎批評指正。

  請尊重作者勞動成果,轉載請标明原文連結:

   http://www.cnblogs.com/dolphin0520/p/3913517.html

  在Java中,一個應用程式對應着一個JVM執行個體(也有地方稱為JVM程序),一般來說名字預設為java.exe或者javaw.exe(windows下可以通過任務管理器檢視)。Java采用的是單線程程式設計模型,即在我們自己的程式中如果沒有主動建立線程的話,隻會建立一個線程,通常稱為主線程。但是要注意,雖然隻有一個線程來執行任務,不代表JVM中隻有一個線程,JVM執行個體在建立的時候,同時會建立很多其他的線程(比如垃圾收集器線程)。

  由于Java采用的是單線程程式設計模型,是以在進行UI程式設計時要注意将耗時的操作放在子線程中進行,以避免阻塞主線程(在UI程式設計時,主線程即UI線程,用來處理使用者的互動事件)。

  在java中如果要建立線程的話,一般有兩種方式:1)繼承Thread類;2)實作Runnable接口。

  1.繼承Thread類

  繼承Thread類的話,必須重寫run方法,在run方法中定義需要執行的任務。

1

2

3

4

5

6

7

8

9

10

11

12

<code>class</code> <code>MyThread </code><code>extends</code> <code>Thread{</code>

<code>    </code><code>private</code> <code>static</code> <code>int</code> <code>num = </code><code>0</code><code>;</code>

<code>    </code> 

<code>    </code><code>public</code> <code>MyThread(){</code>

<code>        </code><code>num++;</code>

<code>    </code><code>}</code>

<code>    </code><code>@Override</code>

<code>    </code><code>public</code> <code>void</code> <code>run() {</code>

<code>        </code><code>System.out.println(</code><code>"主動建立的第"</code><code>+num+</code><code>"個線程"</code><code>);</code>

<code>}</code>

   建立好了自己的線程類之後,就可以建立線程對象了,然後通過start()方法去啟動線程。注意,不是調用run()方法啟動線程,run方法中隻是定義需要執行的任務,如果調用run方法,即相當于在主線程中執行run方法,跟普通的方法調用沒有任何差別,此時并不會建立一個新的線程來執行定義的任務。

13

14

15

16

17

18

19

20

<code>public</code> <code>class</code> <code>Test {</code>

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(String[] args)  {</code>

<code>        </code><code>MyThread thread = </code><code>new</code> <code>MyThread();</code>

<code>        </code><code>thread.start();</code>

   在上面代碼中,通過調用start()方法,就會建立一個新的線程了。為了厘清start()方法調用和run()方法調用的差別,請看下面一個例子:

21

22

23

<code>        </code><code>System.out.println(</code><code>"主線程ID:"</code><code>+Thread.currentThread().getId());</code>

<code>        </code><code>MyThread thread1 = </code><code>new</code> <code>MyThread(</code><code>"thread1"</code><code>);</code>

<code>        </code><code>thread1.start();</code>

<code>        </code><code>MyThread thread2 = </code><code>new</code> <code>MyThread(</code><code>"thread2"</code><code>);</code>

<code>        </code><code>thread2.run();</code>

<code>    </code><code>private</code> <code>String name;</code>

<code>    </code><code>public</code> <code>MyThread(String name){</code>

<code>        </code><code>this</code><code>.name = name;</code>

<code>        </code><code>System.out.println(</code><code>"name:"</code><code>+name+</code><code>" 子線程ID:"</code><code>+Thread.currentThread().getId());</code>

   運作結果:

Java并發程式設計:如何建立線程?

  從輸出結果可以得出以下結論:

  1)thread1和thread2的線程ID不同,thread2和主線程ID相同,說明通過run方法調用并不會建立新的線程,而是在主線程中直接運作run方法,跟普通的方法調用沒有任何差別;

  2)雖然thread1的start方法調用在thread2的run方法前面調用,但是先輸出的是thread2的run方法調用的相關資訊,說明新線程建立的過程不會阻塞主線程的後續執行。

  2.實作Runnable接口

  在Java中建立線程除了繼承Thread類之外,還可以通過實作Runnable接口來實作類似的功能。實作Runnable接口必須重寫其run方法。

下面是一個例子:

<code>        </code><code>System.out.println(</code><code>"主線程ID:"</code><code>+Thread.currentThread().getId());</code>

<code>        </code><code>MyRunnable runnable = </code><code>new</code> <code>MyRunnable();</code>

<code>        </code><code>Thread thread = </code><code>new</code> <code>Thread(runnable);</code>

<code>class</code> <code>MyRunnable </code><code>implements</code> <code>Runnable{</code>

<code>    </code><code>public</code> <code>MyRunnable() {</code>

<code>        </code> 

<code>        </code><code>System.out.println(</code><code>"子線程ID:"</code><code>+Thread.currentThread().getId());</code>

   Runnable的中文意思是“任務”,顧名思義,通過實作Runnable接口,我們定義了一個子任務,然後将子任務交由Thread去執行。注意,這種方式必須将Runnable作為Thread類的參數,然後通過Thread的start方法來建立一個新線程來執行該子任務。如果調用Runnable的run方法的話,是不會建立新線程的,這根普通的方法調用沒有任何差別。

  事實上,檢視Thread類的實作源代碼會發現Thread類是實作了Runnable接口的。

  在Java中,這2種方式都可以用來建立線程去執行子任務,具體選擇哪一種方式要看自己的需求。直接繼承Thread類的話,可能比實作Runnable接口看起來更加簡潔,但是由于Java隻允許單繼承,是以如果自定義類需要繼承其他類,則隻能選擇實作Runnable接口。

   在Java中,可以通過兩種方式來建立程序,總共涉及到5個主要的類。

  第一種方式是通過Runtime.exec()方法來建立一個程序,第二種方法是通過ProcessBuilder的start方法來建立程序。下面就來講一講這2種方式的差別和聯系。

  首先要講的是Process類,Process類是一個抽象類,在它裡面主要有幾個抽象的方法,這個可以通過檢視Process類的源代碼得知:

  位于java.lang.Process路徑下:

<code>public</code> <code>abstract</code> <code>class</code> <code>Process</code>

<code>{</code>

<code>   </code> 

<code>    </code><code>abstract</code> <code>public</code> <code>OutputStream getOutputStream();   </code><code>//擷取程序的輸出流</code>

<code>     </code> 

<code>    </code><code>abstract</code> <code>public</code> <code>InputStream getInputStream();    </code><code>//擷取程序的輸入流</code>

<code>    </code><code>abstract</code> <code>public</code> <code>InputStream getErrorStream();   </code><code>//擷取程序的錯誤流</code>

<code>    </code><code>abstract</code> <code>public</code> <code>int</code> <code>waitFor() </code><code>throws</code> <code>InterruptedException;   </code><code>//讓程序等待</code>

<code> </code> 

<code>    </code><code>abstract</code> <code>public</code> <code>int</code> <code>exitValue();   </code><code>//擷取程序的退出标志</code>

<code>    </code><code>abstract</code> <code>public</code> <code>void</code> <code>destroy();   </code><code>//摧毀程序</code>

  1)通過ProcessBuilder建立程序

  ProcessBuilder是一個final類,它有兩個構造器:

<code>public</code> <code>final</code> <code>class</code> <code>ProcessBuilder</code>

<code>    </code><code>private</code> <code>List&lt;String&gt; command;</code>

<code>    </code><code>private</code> <code>File directory;</code>

<code>    </code><code>private</code> <code>Map&lt;String,String&gt; environment;</code>

<code>    </code><code>private</code> <code>boolean</code> <code>redirectErrorStream;</code>

<code>    </code><code>public</code> <code>ProcessBuilder(List&lt;String&gt; command) {</code>

<code>    </code><code>if</code> <code>(command == </code><code>null</code><code>)</code>

<code>        </code><code>throw</code> <code>new</code> <code>NullPointerException();</code>

<code>    </code><code>this</code><code>.command = command;</code>

<code>    </code><code>public</code> <code>ProcessBuilder(String... command) {</code>

<code>    </code><code>this</code><code>.command = </code><code>new</code> <code>ArrayList&lt;String&gt;(command.length);</code>

<code>    </code><code>for</code> <code>(String arg : command)</code>

<code>        </code><code>this</code><code>.command.add(arg);</code>

<code>....</code>

   構造器中傳遞的是需要建立的程序的指令參數,第一個構造器是将指令參數放進List當中傳進去,第二構造器是以不定長字元串的形式傳進去。

  那麼我們接着往下看,前面提到是通過ProcessBuilder的start方法來建立一個新程序的,我們看一下start方法中具體做了哪些事情。下面是start方法的具體實作源代碼:

24

25

26

27

28

29

30

31

<code>public</code> <code>Process start() </code><code>throws</code> <code>IOException {</code>

<code>// Must convert to array first -- a malicious user-supplied</code>

<code>// list might try to circumvent the security check.</code>

<code>String[] cmdarray = command.toArray(</code><code>new</code> <code>String[command.size()]);</code>

<code>for</code> <code>(String arg : cmdarray)</code>

<code>    </code><code>if</code> <code>(arg == </code><code>null</code><code>)</code>

<code>    </code><code>throw</code> <code>new</code> <code>NullPointerException();</code>

<code>// Throws IndexOutOfBoundsException if command is empty</code>

<code>String prog = cmdarray[</code><code>0</code><code>];</code>

<code>SecurityManager security = System.getSecurityManager();</code>

<code>if</code> <code>(security != </code><code>null</code><code>)</code>

<code>    </code><code>security.checkExec(prog);</code>

<code>String dir = directory == </code><code>null</code> <code>? </code><code>null</code> <code>: directory.toString();</code>

<code>try</code> <code>{</code>

<code>    </code><code>return</code> <code>ProcessImpl.start(cmdarray,</code>

<code>                 </code><code>environment,</code>

<code>                 </code><code>dir,</code>

<code>                 </code><code>redirectErrorStream);</code>

<code>} </code><code>catch</code> <code>(IOException e) {</code>

<code>    </code><code>// It's much easier for us to create a high-quality error</code>

<code>    </code><code>// message than the low-level C code which found the problem.</code>

<code>    </code><code>throw</code> <code>new</code> <code>IOException(</code>

<code>    </code><code>"Cannot run program \""</code> <code>+ prog + </code><code>"\""</code>

<code>    </code><code>+ (dir == </code><code>null</code> <code>? </code><code>""</code> <code>: </code><code>" (in directory \""</code> <code>+ dir + </code><code>"\")"</code><code>)</code>

<code>    </code><code>+ </code><code>": "</code> <code>+ e.getMessage(),</code>

<code>    </code><code>e);</code>

   該方法傳回一個Process對象,該方法的前面部分相當于是根據指令參數以及設定的工作目錄進行一些參數設定,最重要的是try語句塊裡面的一句:

<code>return</code> <code>ProcessImpl.start(cmdarray,</code>

<code>                    </code><code>environment,</code>

<code>                    </code><code>dir,</code>

<code>                    </code><code>redirectErrorStream);</code>

   說明真正建立程序的是這一句,注意調用的是ProcessImpl類的start方法,此處可以知道start必然是一個靜态方法。那麼ProcessImpl又是什麼類呢?該類同樣位于java.lang.ProcessImpl路徑下,看一下該類的具體實作:

  ProcessImpl也是一個final類,它繼承了Process類:

<code>final</code> <code>class</code> <code>ProcessImpl </code><code>extends</code> <code>Process {</code>

<code>    </code><code>// System-dependent portion of ProcessBuilder.start()</code>

<code>    </code><code>static</code> <code>Process start(String cmdarray[],</code>

<code>             </code><code>java.util.Map&lt;String,String&gt; environment,</code>

<code>             </code><code>String dir,</code>

<code>             </code><code>boolean</code> <code>redirectErrorStream)</code>

<code>    </code><code>throws</code> <code>IOException</code>

<code>    </code><code>{</code>

<code>    </code><code>String envblock = ProcessEnvironment.toEnvironmentBlock(environment);</code>

<code>    </code><code>return</code> <code>new</code> <code>ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);</code>

<code> </code><code>....</code>

   這是ProcessImpl類的start方法的具體實作,而事實上start方法中是通過這句來建立一個ProcessImpl對象的:

<code>return</code> <code>new</code> <code>ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);</code>

   而在ProcessImpl中對Process類中的幾個抽象方法進行了具體實作。

  說明事實上通過ProcessBuilder的start方法建立的是一個ProcessImpl對象。

  下面看一下具體使用ProcessBuilder建立程序的例子,比如我要通過ProcessBuilder來啟動一個程序打開cmd,并擷取ip位址資訊,那麼可以這麼寫:

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(String[] args) </code><code>throws</code> <code>IOException  {</code>

<code>        </code><code>ProcessBuilder pb = </code><code>new</code> <code>ProcessBuilder(</code><code>"cmd"</code><code>,</code><code>"/c"</code><code>,</code><code>"ipconfig/all"</code><code>);</code>

<code>        </code><code>Process process = pb.start();</code>

<code>        </code><code>Scanner scanner = </code><code>new</code> <code>Scanner(process.getInputStream());</code>

<code>        </code><code>while</code><code>(scanner.hasNextLine()){</code>

<code>            </code><code>System.out.println(scanner.nextLine());</code>

<code>        </code><code>}</code>

<code>        </code><code>scanner.close();</code>

   第一步是最關鍵的,就是将指令字元串傳給ProcessBuilder的構造器,一般來說,是把字元串中的每個獨立的指令作為一個單獨的參數,不過也可以按照順序放入List中傳進去。

  至于其他很多具體的用法不在此進行贅述,比如通過ProcessBuilder的environment方法和directory(File directory)設定程序的環境變量以及工作目錄等,感興趣的朋友可以檢視相關API文檔。

  2)通過Runtime的exec方法來建立程序

  首先還是來看一下Runtime類和exec方法的具體實作,Runtime,顧名思義,即運作時,表示目前程序所在的虛拟機執行個體。

  由于任何程序隻會運作于一個虛拟機執行個體當中,是以在Runtime中采用了單例模式,即隻會産生一個虛拟機執行個體:

<code>public</code> <code>class</code> <code>Runtime {</code>

<code>    </code><code>private</code> <code>static</code> <code>Runtime currentRuntime = </code><code>new</code> <code>Runtime();</code>

<code>    </code><code>/**</code>

<code>     </code><code>* Returns the runtime object associated with the current Java application.</code>

<code>     </code><code>* Most of the methods of class &lt;code&gt;Runtime&lt;/code&gt; are instance</code>

<code>     </code><code>* methods and must be invoked with respect to the current runtime object.</code>

<code>     </code><code>*</code>

<code>     </code><code>* @return  the &lt;code&gt;Runtime&lt;/code&gt; object associated with the current</code>

<code>     </code><code>*          Java application.</code>

<code>     </code><code>*/</code>

<code>    </code><code>public</code> <code>static</code> <code>Runtime getRuntime() {</code>

<code>    </code><code>return</code> <code>currentRuntime;</code>

<code>    </code><code>/** Don't let anyone else instantiate this class */</code>

<code>    </code><code>private</code> <code>Runtime() {}</code>

<code>    </code><code>...</code>

<code> </code><code>}</code>

   從這裡可以看出,由于Runtime類的構造器是private的,是以隻有通過getRuntime去擷取Runtime的執行個體。接下來着重看一下exec方法 實作,在Runtime中有多個exec的不同重載實作,但真正最後執行的是這個版本的exec方法:

<code>public</code> <code>Process exec(String[] cmdarray, String[] envp, File dir)</code>

<code>   </code><code>throws</code> <code>IOException {</code>

<code>   </code><code>return</code> <code>new</code> <code>ProcessBuilder(cmdarray)</code>

<code>       </code><code>.environment(envp)</code>

<code>       </code><code>.directory(dir)</code>

<code>       </code><code>.start();</code>

<code>   </code><code>}</code>

   可以發現,事實上通過Runtime類的exec建立程序的話,最終還是通過ProcessBuilder類的start方法來建立的。

  下面看一個例子,看一下通過Runtime的exec如何建立程序,還是前面的例子,調用cmd,擷取ip位址資訊:

<code>        </code><code>String cmd = </code><code>"cmd "</code><code>+</code><code>"/c "</code><code>+</code><code>"ipconfig/all"</code><code>;</code>

<code>        </code><code>Process process = Runtime.getRuntime().exec(cmd);</code>

   要注意的是,exec方法不支援不定長參數(ProcessBuilder是支援不定長參數的),是以必須先把指令參數拼接好再傳進去。

  關于在Java中如何建立線程和程序的話,暫時就講這麼多了,感興趣的朋友可以參考相關資料、

  參考資料:

  《Java程式設計思想》

本文轉載自海 子部落格園部落格,原文連結:http://www.cnblogs.com/dolphin0520/p/3913517.html如需轉載自行聯系原作者