創建線程的幾種方式


編寫多線程程序是為了實現多任務的並發執行,從而能夠更好地與用戶交互。一般有四種方法,Thread,Runnable,Callable,使用Executor框架來創建線程池。

Runnable和Callable的區別是,
(1)Callable規定的方法是call(),Runnable規定的方法是run().
(2)Callable的任務執行后可返回值,而Runnable的任務是不能返回值得
(3)call方法可以拋出異常,run方法不可以
(4)運行Callable任務可以拿到一個Future對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。通過Future對象可以了解任務執行情況,可取消任務的執行,還可獲取執行結果。

1、通過實現Runnable接口來創建Thread線程:

  步驟1:創建實現Runnable接口的類:

         
class SomeRunnable implements Runnable
{
    public void run()
    {
      //do something here
    }
}

步驟2:創建一個類對象:

       Runnable oneRunnable = new SomeRunnable();

步驟3:由Runnable創建一個Thread對象:

       Thread oneThread = new Thread(oneRunnable);

步驟4:啟動線程:

        oneThread.start();

至此,一個線程就創建完成了。

注釋:線程的執行流程很簡單,當執行代碼oneThread.start();時,就會執行oneRunnable對象中的void run();方法,

該方法執行完成后,線程就消亡了。

2、與方法1類似,通過實現Callable接口來創建Thread線程:其中,Callable接口(也只有一個方法)定義如下:

public interface Callable<V>   
{   
    V call() throws Exception;   

步驟1:創建實現Callable接口的類SomeCallable<Integer>(略);

步驟2:創建一個類對象:

      Callable<Integer> oneCallable = new SomeCallable<Integer>();

步驟3:由Callable<Integer>創建一個FutureTask<Integer>對象:

      FutureTask<Integer> oneTask = new FutureTask<Integer>(oneCallable);

      注釋:FutureTask<Integer>是一個包裝器,它通過接受Callable<Integer>來創建,它同時實現了Future和Runnable接口。
步驟4:由FutureTask<Integer>創建一個Thread對象:

       Thread oneThread = new Thread(oneTask);

步驟5:啟動線程:

       oneThread.start();

至此,一個線程就創建完成了。

3、通過繼承Thread類來創建一個線程:

步驟1:定義一個繼承Thread類的子類:

       

class SomeThead extends Thraad
{
    public void run()
    {
     //do something here
    }
}

步驟2:構造子類的一個對象:

      SomeThread oneThread = new SomeThread();

步驟3:啟動線程:

      oneThread.start();

至此,一個線程就創建完成了。

       注釋:這種創建線程的方法不夠好,主要是因為其涉及運行機制問題,影響程序性能。

 

4、使用Executor框架來創建線程池

 在Java 5之后,並發編程引入了一堆新的啟動、調度和管理線程的API。Executor框架便是Java 5中引入的,其內部使用了線程池機制,它在java.util.cocurrent 包下,通過該框架來控制線程的啟動、執行和關閉,可以簡化並發編程的操作。因此,在Java 5之后,通過Executor來啟動線程比使用Thread的start方法更好,除了更易管理,效率更好(用線程池實現,節約開銷)外,還有關鍵的一點:有助於避免this逃逸問題——如果我們在構造器中啟動一個線程,因為另一個任務可能會在構造器結束之前開始執行,此時可能會訪問到初始化了一半的對象用Executor在構造器中。

 

    Executor框架包括:線程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

 

    Executor接口中之定義了一個方法execute(Runnable command),該方法接收一個Runable實例,它用來執行一個任務,任務即一個實現了Runnable接口的類。ExecutorService接口繼承自Executor接口,它提供了更豐富的實現多線程的方法,比如,ExecutorService提供了關閉自己的方法,以及可為跟蹤一個或多個異步任務執行狀況而生成 Future 的方法。 可以調用ExecutorService的shutdown()方法來平滑地關閉 ExecutorService,調用該方法后,將導致ExecutorService停止接受任何新的任務且等待已經提交的任務執行完成(已經提交的任務會分兩類:一類是已經在執行的,另一類是還沒有開始執行的),當所有已經提交的任務執行完畢后將會關閉ExecutorService。因此我們一般用該接口來實現和管理多線程。

 

    ExecutorService的生命周期包括三種狀態:運行、關閉、終止。創建后便進入運行狀態,當調用了shutdown()方法時,便進入關閉狀態,此時意味着ExecutorService不再接受新的任務,但它還在執行已經提交了的任務,當素有已經提交了的任務執行完后,便到達終止狀態。如果不調用shutdown()方法,ExecutorService會一直處在運行狀態,不斷接收新的任務,執行新的任務,服務器端一般不需要關閉它,保持一直運行即可。

 

 

    Executors提供了一系列工廠方法用於創先線程池,返回的線程池都實現了ExecutorService接口。   

    public static ExecutorService newFixedThreadPool(int nThreads)

    創建固定數目線程的線程池。

    public static ExecutorService newCachedThreadPool()

    創建一個可緩存的線程池,調用execute將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線   程並添加到池中。終止並從緩存中移除那些已有 60 秒鍾未被使用的線程。

    public static ExecutorService newSingleThreadExecutor()

    創建一個單線程化的Executor。

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

    創建一個支持定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類。

 

    這四種方法都是用的Executors中的ThreadFactory建立的線程,下面就以上四個方法做個比較

 




newCachedThreadPool()                                                                                                                                         

-緩存型池子,先查看池中有沒有以前建立的線程,如果有,就 reuse.如果沒有,就建一個新的線程加入池中
-緩存型池子通常用於執行一些生存期很短的異步型任務
 因此在一些面向連接的daemon型SERVER中用得不多。但對於生存期短的異步任務,它是Executor的首選。
-能reuse的線程,必須是timeout IDLE內的池中線程,缺省     timeout是60s,超過這個IDLE時長,線程實例將被終止及移出池。
  注意,放入CachedThreadPool的線程不必擔心其結束,超過TIMEOUT不活動,其會自動被終止。



newFixedThreadPool(int)                                                      

-newFixedThreadPool與cacheThreadPool差不多,也是能reuse就用,但不能隨時建新的線程

-其獨特之處:任意時間點,最多只能有固定數目的活動線程存在,此時如果有新的線程要建立,只能放在另外的隊列中等待直到當前的線程中某個線程終止直接被移出池子
-和cacheThreadPool不同,FixedThreadPool沒有IDLE機制(可能也有,但既然文檔沒提,肯定非常長,類似依賴上層的TCP或UDP IDLE機制之類的),所以FixedThreadPool多數針對一些很穩定很固定的正規並發線程,多用於服務器
-從方法的源代碼看,cache池和fixed 池調用的是同一個底層 池,只不過參數不同:
fixed池線程數固定,並且是0秒IDLE(無IDLE)    
cache池線程數支持0-Integer.MAX_VALUE(顯然完全沒考慮主機的資源承受能力),60秒IDLE  


newScheduledThreadPool(int)

-調度型線程池
-這個池子里的線程可以按schedule依次delay執行,或周期執行

SingleThreadExecutor()

-單例線程,任意時間池中只能有一個線程
-用的是和cache池和fixed池相同的底層池,但線程數目是1-1,0秒IDLE(無IDLE)


 

    一般來說,CachedTheadPool在程序執行過程中通常會創建與所需數量相同的線程,然后在它回收舊線程時停止創建新線程,因此它是合理的Executor的首選,只有當這種方式會引發問題時(比如需要大量長時間面向連接的線程時),才需要考慮用FixedThreadPool。(該段話摘自《Thinking in Java》第四版)

 

                         

Executor執行Runnable任務

    通過Executors的以上四個靜態工廠方法獲得 ExecutorService實例,而后調用該實例的execute(Runnable command)方法即可。一旦Runnable任務傳遞到execute()方法,該方法便會自動在一個線程上

 

[java] view pl
 
import java.util.concurrent.ExecutorService;   
import java.util.concurrent.Executors;   
  
public class TestCachedThreadPool{   
    public static void main(String[] args){   
        ExecutorService executorService = Executors.newCachedThreadPool();   
//      ExecutorService executorService = Executors.newFixedThreadPool(5);  
//      ExecutorService executorService = Executors.newSingleThreadExecutor();  
        for (int i = 0; i < 5; i++){   
            executorService.execute(new TestRunnable());   
            System.out.println("************* a" + i + " *************");   
        }   
        executorService.shutdown();   
    }   
}   
  
class TestRunnable implements Runnable{   
    public void run(){   
        System.out.println(Thread.currentThread().getName() + "線程被調用了。");   
    }   
}  

 

 

 

   某次執行后的結果如下:

 

 

 

   從結果中可以看出,pool-1-thread-1和pool-1-thread-2均被調用了兩次,這是隨機的,execute會首先在線程池中選擇一個已有空閑線程來執行任務,如果線程池中沒有空閑線程,它便會創建一個新的線程來執行任務。

 

 

Executor執行Callable任務

    在Java 5之后,任務分兩類:一類是實現了Runnable接口的類,一類是實現了Callable接口的類。兩者都可以被ExecutorService執行,但是Runnable任務沒有返回值,而Callable任務有返回值。並且Callable的call()方法只能通過ExecutorService的submit(Callable<T> task) 方法來執行,並且返回一個 <T>Future<T>,是表示任務等待完成的 Future。

 

    Callable接口類似於Runnable,兩者都是為那些其實例可能被另一個線程執行的類設計的。但是 Runnable 不會返回結果,並且無法拋出經過檢查的異常而Callable又返回結果,而且當獲取返回結果時可能會拋出異常。Callable中的call()方法類似Runnable的run()方法,區別同樣是有返回值,后者沒有。

 

    當將一個Callable的對象傳遞給ExecutorService的submit方法,則該call方法自動在一個線程上執行,並且會返回執行結果Future對象。同樣,將Runnable的對象傳遞給ExecutorService的submit方法,則該run方法自動在一個線程上執行,並且會返回執行結果Future對象,但是在該Future對象上調用get方法,將返回null。

 

    下面給出一個Executor執行Callable任務的示例代碼:

 

import java.util.ArrayList;   
import java.util.List;   
import java.util.concurrent.*;   
  
public class CallableDemo{   
    public static void main(String[] args){   
        ExecutorService executorService = Executors.newCachedThreadPool();   
        List<Future<String>> resultList = new ArrayList<Future<String>>();   
  
        //創建10個任務並執行   
        for (int i = 0; i < 10; i++){   
            //使用ExecutorService執行Callable類型的任務,並將結果保存在future變量中   
            Future<String> future = executorService.submit(new TaskWithResult(i));   
            //將任務執行結果存儲到List中   
            resultList.add(future);   
        }   
  
        //遍歷任務的結果   
        for (Future<String> fs : resultList){   
                try{   
                    while(!fs.isDone);//Future返回如果沒有完成,則一直循環等待,直到Future返回完成  
                    System.out.println(fs.get());     //打印各個線程(任務)執行的結果   
                }catch(InterruptedException e){   
                    e.printStackTrace();   
                }catch(ExecutionException e){   
                    e.printStackTrace();   
                }finally{   
                    //啟動一次順序關閉,執行以前提交的任務,但不接受新任務  
                    executorService.shutdown();   
                }   
        }   
    }   
}   
  
  
class TaskWithResult implements Callable<String>{   
    private int id;   
  
    public TaskWithResult(int id){   
        this.id = id;   
    }   
  
    /**  
     * 任務的具體過程,一旦任務傳給ExecutorService的submit方法, 
     * 則該方法自動在一個線程上執行 
     */   
    public String call() throws Exception {  
        System.out.println("call()方法被自動調用!!!    " + Thread.currentThread().getName());   
        //該返回結果將被Future的get方法得到  
        return "call()方法被自動調用,任務返回的結果是:" + id + "    " + Thread.currentThread().getName();   
    }   
}  

 

 

    某次執行結果如下:

 

   

 

    從結果中可以同樣可以看出,submit也是首先選擇空閑線程來執行任務,如果沒有,才會創建新的線程來執行任務。另外,需要注意:如果Future的返回尚未完成,則get()方法會阻塞等待,直到Future完成返回,可以通過調用isDone()方法判斷Future是否完成了返回。

 

 

 

自定義線程池

    自定義線程池,可以用ThreadPool Executor類創建,它有多個構造方法來創建線程池,用該類很容易實現自定義的線程池,這里先貼上示例程序:

] view plai

import java.util.concurrent.ArrayBlockingQueue;   
import java.util.concurrent.BlockingQueue;   
import java.util.concurrent.ThreadPoolExecutor;   
import java.util.concurrent.TimeUnit;   
  
public class ThreadPoolTest{   
    public static void main(String[] args){   
        //創建等待隊列   
        BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);   
        //創建線程池,池中保存的線程數為3,允許的最大線程數為5  
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue);   
        //創建七個任務   
        Runnable t1 = new MyThread();   
        Runnable t2 = new MyThread();   
        Runnable t3 = new MyThread();   
        Runnable t4 = new MyThread();   
        Runnable t5 = new MyThread();   
        Runnable t6 = new MyThread();   
        Runnable t7 = new MyThread();   
        //每個任務會在一個線程上執行  
        pool.execute(t1);   
        pool.execute(t2);   
        pool.execute(t3);   
        pool.execute(t4);   
        pool.execute(t5);   
        pool.execute(t6);   
        pool.execute(t7);   
        //關閉線程池   
        pool.shutdown();   
    }   
}   
  
class MyThread implements Runnable{   
    @Override   
    public void run(){   
        System.out.println(Thread.currentThread().getName() + "正在執行。。。");   
        try{   
            Thread.sleep(100);   
        }catch(InterruptedException e){   
            e.printStackTrace();   
        }   
    }   
}  

 

 

copy在CODE上查看代碼片

    運行結果如下:

 

 

    從結果中可以看出,七個任務是在線程池的三個線程上執行的。這里簡要說明下用到的ThreadPoolExecuror類的構造方法中各個參數的含義。   

 

public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long         keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)

 

corePoolSize:線程池中所保存的線程數,包括空閑線程。

maximumPoolSize:池中允許的最大線程數。

keepAliveTime:當線程數大於核心數時,該參數為所有的任務終止前,多余的空閑線程等待新任務的最長時間。

unit:等待時間的單位。

workQueue:任務執行前保存任務的隊列,僅保存由execute方法提交的Runnable任務。

 


免責聲明!

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



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