天天看點

java 建立線程的三種方式、建立線程池的四種方式

概要:

java建立線程的三種方式:
  1.     繼承Thread類建立線程類
  2.     實作Runnable接口
  3.     通過Callable和Future建立線程

java建立線程池的四種方式:

     newCachedThreadPool 建立一個可緩存的線程池,如果線程池長度超過處理需求,可靈活回收空閑線程,若無可回收,則建立線程

    newFixedThreadPool 建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待

    newScheduledThreadPool 建立一個定長線程池,支援定時及周期性任務執行

    newSingleThreadExecutor 建立一個單線程化的線程池,它隻會唯一的工作線程來執行任務,保證所有任務按照指定

順序(FIFO,LIFO,優先級)執行

線程池的優點:

     a. 重用存在的線程,減少對象建立、消亡的開銷,性能佳。

     b. 可有效控制最大并發線程數,提高系統資源的使用率,同時避免過多資源競争,避免堵塞。

     c. 提供定時執行、定期執行、單線程、并發數控制等功能。

第一 Java中建立線程主要有三種方式:

1、繼承Thread類建立線程類 (extends)

(1)定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。是以把run()方法稱為執行體(線程體)。

(2)建立Thread子類的執行個體,即建立了線程對象。

(3)調用線程對象的start()方法來啟動該線程。  

public class FirstThreadTest extends Thread{  
    int i = 0;  
    //重寫run方法,run方法的方法體就是現場執行體  
    public void run()  
    {  
        for(;i<100;i++){  
        log.info(getName()+"  "+i);  
        }  
    }  

    public static void main(String[] args)  
    {  
        for(int i = 0;i< 100;i++)  
        {  
            log.info(Thread.currentThread().getName()+"  : "+i);  
            if(i==20)  
            {  
                new FirstThreadTest().start();  
                new FirstThreadTest().start();  
            }  
        }  
    }  
} 
           

上述代碼中Thread.currentThread()方法傳回目前正在執行的線程對象。GetName()方法傳回調用該方法的線程的名字。

2、通過Runnable接口建立線程類

(1)定義runnable接口的實作類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。

(2)建立 Runnable實作類的執行個體,并以此執行個體作為Thread的target來建立Thread對象,該Thread對象才是真正的線程對象。

(3)調用線程對象的start()方法來啟動該線程。

public class RunnableThreadTest implements Runnable  
{  
  
    private int i;  
    public void run()  
    {  
        for(i = 0;i <100;i++)  
        {  
           log.info(Thread.currentThread().getName()+" "+i);  
        }  
    }  

    public static void main(String[] args)  
    {  
        for(int i = 0;i < 100;i++)  
        {  
            log.info(Thread.currentThread().getName()+" "+i);  
            if(i==20)  
            {  
                RunnableThreadTest runner= new RunnableThreadTest();  
                new Thread(runner,"新線程1").start();  
                new Thread(runner,"新線程2").start();  
            }  
        }  
    }   
}  
           

線程的執行流程很簡單,當執行代碼start()時,就會執行對象中重寫的void run()方法,該方法執行完成後,線程就消亡了。

3、通過Callable和Future建立線程

(1)建立Callable接口的實作類,并實作call()方法,該call()方法将作為線程執行體,并且有傳回值。

public interface Callable
{
  V call() throws Exception;
}
           

(2)建立Callable實作類的執行個體,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方

法的傳回值。(FutureTask是一個包裝器,它通過接受Callable來建立,它同時實作了Future和Runnable接口。)

(3)使用FutureTask對象作為Thread對象的target建立并啟動新線程。

(4)調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值

public class CallableThreadTest implements Callable<Integer>  
{  
  
    public static void main(String[] args)  
    {  
        CallableThreadTest ctt = new CallableThreadTest();  
        FutureTask<Integer> ft = new FutureTask<>(ctt);  
        for(int i = 0;i < 100;i++)  
        {  
            log.info(Thread.currentThread().getName()+" 的循環變量i的值"+i);  
            if(i==20)  
            {  
                new Thread(ft,"有傳回值的線程").start();  
            }  
        }  
        try  
        {  
            log.info("子線程的傳回值:"+ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        }  
  
    }  
  
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            log.info(Thread.currentThread().getName()+" "+i);  
        }  
        return i;  
    }  
  
}  
           

二、建立線程的三種方式的對比

1、采用實作Runnable、Callable接口的方式建立多線程

      優勢:

       線程類隻是實作了Runnable接口或Callable接口,還可以繼承其他類。

       在這種方式下,多個線程可以共享同一個target對象,是以非常适合多個相同線程來處理同一份資源的情況,進而可以将CPU、代碼和資料分開,形成清晰的模型,較好地展現了面向對象的思想。

       劣勢:

     程式設計稍微複雜,如果要通路目前線程,則必須使用Thread.currentThread()方法。

2、使用繼承Thread類的方式建立多線程

      優勢:

      編寫簡單,如果需要通路目前線程,則無需使用Thread.currentThread()方法,直接使用this即可獲得目前線程。

      劣勢:

      線程類已經繼承了Thread類,是以不能再繼承其他父類。

3、Runnable和Callable的差別

     (1) Callable規定(重寫)的方法是call(),Runnable規定(重寫)的方法是run()。

     (2) Callable的任務執行後可傳回值,而Runnable的任務是不能傳回值的。

     (3) call方法可以抛出異常,run方法不可以。

     (4) 運作Callable任務可以拿到一個Future對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的

完成,并檢索計算的結果。通過Future對象可以了解任務執行情況,可取消任務的執行,還可擷取執行結果future.get()。

第二 建立四種線程池的方式

1、new Thread的弊端

執行一個異步任務你還隻是如下new Thread嗎?

new Thread(new Runnable() {


  @Override

  public void run() {

    // TODO Auto-generated method stub

    }

  }

).start();
           

那你就out太多了,new Thread的弊端如下:

     a. 每次new Thread建立對象性能差。

     b. 線程缺乏統一管理,可能無限制建立線程,互相之間競争,及可能占用過多系統資源導緻當機或oom(out of memory)。

     c. 缺乏更多功能,如定時執行、定期執行、線程中斷。

相比new Thread,Java提供的四種線程池的好處在于:

          a. 重用存在的線程,減少對象建立、消亡的開銷,性能佳。

          b. 可有效控制最大并發線程數,提高系統資源的使用率,同時避免過多資源競争,避免堵塞。

          c. 提供定時執行、定期執行、單線程、并發數控制等功能。

2、Java 線程池

Java通過Executors提供四種線程池,分别為:

newCachedThreadPool建立一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則建立線程。

newFixedThreadPool 建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。

newScheduledThreadPool 建立一個定長線程池,支援定時及周期性任務執行。

newSingleThreadExecutor 建立一個單線程化的線程池,它隻會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

(1) newCachedThreadPool:

建立一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則建立線程。示例代碼如下:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

  for (int i = 0; i < 10; i++) {

    final int index = i;

  try {

    Thread.sleep(index * 1000);

   } catch (InterruptedException e) {

      e.printStackTrace();

  }



   cachedThreadPool.execute(new Runnable() {

     @Override

     public void run() {

        log.info(index);

      }

   });

}
           

線程池為無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次建立線程。

(2) newFixedThreadPool:---  需要指定線程池的大小

建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。示例代碼如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

  for (int i = 0; i < 10; i++) {

  final int index = i;


  fixedThreadPool.execute(new Runnable() {


  @Override

  public void run() {
 
      try {
         log.info(index);
         Thread.sleep(2000);
      } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
       }
    }
  });
}
           

因為線程池大小為3,每個任務輸出index後sleep 2秒,是以每兩秒列印3個數字。

定長線程池的大小最好根據系統資源進行設定。如Runtime.getRuntime().availableProcessors()。可參考PreloadDataCache。

(3)newScheduledThreadPool:

建立一個定長線程池,支援定時及周期性任務執行。延遲執行示例代碼如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

 scheduledThreadPool.schedule(new Runnable() {


      @Override

      public void run() {

         log.info("delay 3 seconds");
       }

 }, 3, TimeUnit.SECONDS);
           

表示延遲3秒執行。

定期執行示例代碼如下:

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

      @Override

      public void run() {

          log.info("delay 1 seconds, and excute every 3 seconds");

      }

}, 1, 3, TimeUnit.SECONDS);
           

表示延遲1秒後每3秒執行一次。

ScheduledExecutorService比Timer更安全,功能更強大

(4)newSingleThreadExecutor:

建立一個單線程化的線程池,它隻會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。示例代碼如下:

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

for (int i = 0; i < 10; i++) {
  final int index = i;
  singleThreadExecutor.execute(new Runnable() {

    @Override
    public void run() {
    try {
        log.info(index);
        Thread.sleep(2000);
     } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
     }
    }
  });
}
           

結果依次輸出,相當于順序執行各個任務。

線程池的作用:

線程池作用就是限制系統中執行線程的數量。

根據系統的環境情況,可以自動或手動設定線程數量,達到運作的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率

不高。用線程池控制線程數量,其他線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒

有等待程序,線程池的這一資源處于等待。當一個新任務需要運作時,如果線程池中有等待的工作線程,就可以開始運作了;

否則進入等待隊列。

為什麼要用線程池:

1.減少了建立和銷毀線程的次數,每個工作線程都可以被重複利用,可執行多個任務。

2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的記憶體,而把伺服器累趴下(每個線程需要大

約1MB記憶體,線程開的越多,消耗的記憶體也就越大,最後當機)。

Java裡面線程池的頂級接口是Executor,但是嚴格意義上講Executor并不是一個線程池,而隻是一個執行線程的工具。真正的

線程池接口是ExecutorService。

比較重要的幾個類:

ExecutorService: 真正的線程池接口。

ScheduledExecutorService: 能和Timer/TimerTask類似,解決那些需要任務重複執行的問題。

ThreadPoolExecutor: ExecutorService的預設實作。

ScheduledThreadPoolExecutor: 繼承ThreadPoolExecutor的ScheduledExecutorService接口實作,周期性任務排程的類實作。

要配置一個線程池是比較複雜的,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,是以

在Executors類裡面提供了一些靜态工廠,生成一些常用的線程池。