多線程學習之路


線程是由操作系統來進行調度的

1、實現多線程的四種方式

  1. 繼承Thread類

    Tread類本質上就是一個實現了Runnable接口的一個實例,代表一個線程的實例。啓動線程的唯一方法是通過Thread的類的start()實例方法。start()是一個本地方法,它將啓動一個新線程,並執行run()方法。

    通過自己的類直接繼承Tread類,並重寫run()方法。

  2. 實現Runnable接口

    因爲繼承是單繼承,所以當自己的類已經繼承了另一個類的時候,就無法使用extends Tread了,可以實現Runnable接口,並實現run()方法。使用時先實例化一個Tread,並傳入自己的對象實例,當執行Tread.start()的時候,Tread的run()方法就會調用目標對象的run()方法

    public void run() {  
      if (target != null) {  
       target.run();  
      }  
    }
    
  3. 實現callable接口,通過futureTask包裝器來創建Tread線程

    public interface Callable<V>   { 
      V call() throws Exception;   
    }
    
    public class SomeCallable<V> extends OtherClass implements Callable<V> {
        @Override
        public V call() throws Exception { 
            return null;
        }
    }
    //第一種,使用Tread
    Callable<V> oneCallable = new SomeCallable<V>();     
    FutureTask<V> oneTask = new FutureTask<V>(oneCallable);      
    Thread oneThread = new Thread(oneTask);   
    oneThread.start(); 
    //第二種,使用線程池
    ExecutorService executor = Executors.newCachedThreadPool();
    Callable<V> oneCallable = new SomeCallable<V>();
    FutureTask<Integer> futureTask = new FutureTask<Integer>(oneCallable);
    executor.submit(futureTask);
    executor.shutdown();
    
  4. 使用ExecutorService、Callable、Future實現有返回結果的線程

    public interface Future<V> {
        boolean cancel(boolean mayInterruptIfRunning);
        boolean isCancelled();//表示任務是否被取消成功,如果任務正常完成前被取消成功,則返回true
        boolean isDone();//表示任務是否已經完成,若任務完成,返回true
        V get() throws InterruptedException, ExecutionException;//會產生阻塞,直到任務執行完畢
        V get(long timeout, TimeUnit unit)//指定時間內沒獲取到結果,直接返回null
            throws InterruptedException, ExecutionException, TimeoutException;
    }
    // 創建一個線程池  
    ExecutorService pool = Executors.newFixedThreadPool(5);  
    // 創建多個有返回值的任務  
    List<Future> list = new ArrayList<Future>();  
    for (int i = 0; i < 5; i++) {  
        Callable c = new MyCallable(i + " ");  
        // 執行任務並獲取Future對象  
        Future f = pool.submit(c);  
        // System.out.println(">>>" + f.get().toString());  
        list.add(f);  
    }  
    // 關閉線程池  
    pool.shutdown();  
    // 獲取所有併發任務的運行結果  
    for (Future f : list) {  
        // 從Future對象上獲取任務的返回值,並輸出到控制檯  
        System.out.println(">>>" + f.get().toString());  
    }  
    

2、線程池

  1. java.uitl.concurrent.ThreadPoolExecutor類是線程池最核心的類

    //構造函數
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    
    • corePoolSize:核心池大小,當線程池中的線程數目達到corePoolSize大小後,就會把任務放到緩存隊列

    • maximumPoolSize:線程池的最大線程數,表示線程池最多能創建多少線程

      當一個新的任務被提交到線程池的時候,1>,先判斷核心池是否已滿,未滿則創建一個新的線程來執行該任務;滿了則去判斷任務隊列是否已滿,未滿則將新提交的任務存儲在工作隊列中;滿了則去判斷最大線程數是否已滿,未滿則創建一個新的工作線程去執行任務;滿了則交給飽和策略來處理這個任務。如果線程池中的線程數大於核心池大小,則當某個線程空閒時間超過keepAliveTime時,該線程會被終止

      一般核心線程池容量很小,最大容量比較大,當核心池滿了以後,會先將任務放在隊列中,然後判斷是否達到了最大線程數,沒有達到就創建線程執行;達到以後,任務就暫存在任務隊列中,直到任務隊列已滿,執行拒絕策略。

    • keepAliveTime:默認情況下,只有當線程池中的線程數量大於核心池時,這個參數纔會起作用

    • unit :是keepAliveTime的時間單位

    • workQueue:是一個阻塞隊列,用來存儲等待執行的任務

    • threadFactory:線程工廠,主要用來創建線程

    • handler:表示當拒絕任務處理時的策略

      • AbortPolicy:丟棄任務並拋出RejectedExecutionException異常
      • DiscardPolicy:丟棄任務,但不拋出異常
      • DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
      • CallerRunsPolicy:由調用線程處理該任務
  2. ThreadPoolExecutor有幾個重要的方法:

    • execute():向線程池提交一個任務,交由線程池去執行
    • submit():也是向線程池提交任務,和execute()的區別是submit()能夠返回任務的執行結果,實際上調用的還是execute(),只不過利用Future()來獲取任務的執行結果
    • shutdown():關閉線程池,此時線程池不能夠接受新的任務,他會等待所有任務執行完畢
    • shutdownNow():關閉線程池,此時線程池不能接受新的任務,並且嘗試終止正在進行的任務
  3. 線程池的狀態:

    volatile int runState;
    static final int RUNNING = 0;
    static final int SHUTDOWN = 1static final int STOP = 2;
    static final int TERMINATED = 3

    runState:表示當前線程池的狀態,使用volatile修飾來保證線程之前的可見性

  4. 線程池中的線程初始化

    默認情況下,創建線程池之後,線程池中是沒有線程的,需要提交任務之後纔會創建線程

    在實際中如果需要線程池創建之後 立即創建線程,可以通過兩個方法來實現:

    • prestartCoreThread():初始化一個核心線程
    • prestartAllCoreThreads():初始化核心池數量的線程
  5. 任務緩存隊列及排隊策略

    workQueue的類型爲BlockingQueue,通常有三種類型:

    • ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小
    • LinkedBlockingQueue:基於鏈表的阻塞隊列,如果創建時沒有指定此隊列的大小,默認爲Integer.MAX_VALUE;
    • SynchronousQueue:這個隊列比較特殊,不會保存提交的任務,而是直接新建一個線程來執行新來的任務
  6. 具體使用

    Executors.newCachedThreadPool();      //創建一個緩衝池,緩衝池容量大小爲Integer.MAX_VALUE
    Executors.newSingleThreadExecutor();  //創建容量爲1的緩衝池
    Executors.newFixedThreadPool(int);    //創建固定容量大小的緩衝池
    

    具體實現:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    

    從它們的具體實現來看,它們實際上也是調用了ThreadPoolExecutor,只不過參數都已配置好了。

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