java併發編程——線程池和Executor介紹

  • 第一部分:概述

早期的應用程序大可能是單線程串行執行的,雖然程序的任務邊界清晰有序,可是執行的效率卻很低,尤爲是執行花費時間較長的操做,會致使大量的等待和堆積。爲了提升程序的執行效率和吞吐量,咱們很天然的會想到多線程,即爲每一個任務都新建一個獨立的線程,這樣就極大地提升了程序的執行效率。但事實上多線程也會帶來不少問題。好比大量的建立線程,這自己就會消耗不少的資源,尤爲是內存,當建立的線程數量超過服務器可以承受的極限時,內存溢出是在所不免的;好比還有其餘穩定性問題,以及多線程形成的程序調用和管理的混亂。因此,綜上所述,咱們須要一個介於二者之間的工具,既能夠建立大量的線程來提升程序的併發性和吞吐量,同時又能夠有序的管理這些線程,可以可控。而Executor接口和線程池技術就是在這種背景下應運而生的。下面分別將這兩種經常使用的併發技術作以介紹和總結。java

  • 第二部分:Executor和ExecutorService接口介紹

java.util.coucurrent包下面爲咱們提供了豐富的併發工具,Executor和ExecutorService接口就是其中比較重要的兩個。緩存

  • Executor接口介紹

public interface Executor{
    void execute(Runnable command);
}

以上是Executor接口的代碼,能夠看出這個藉口很是簡單,就只有一個execute()方法。但他卻爲強大的異步任務執行提供了基礎,它支持不一樣類型的任務執行策略。Executor框架基於生產者消費者模型,它提供了一個標準的方法將任務的提交和任務的執行過程解耦,並用Runnable來表示任務。下面來看一個基於Executor的簡單服務器實現:服務器

public class ExecutorWebServer {
    private static final int LIMIT = 50;
    private static final Executor exe = Executors.newFixedThreadPool(LIMIT);
    
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
        ServerSocket server = new ServerSocket(80);
        while(true){
        	Socket socket = server.accept();
        	Runnable task = new Runnable(){
        		public void run(){
        			doSomeThing(socket);
        		}
        	};
        	exe.execute(task);
        }
	}
}

經過上面列子咱們就很好的把任務的提交和任務的執行分開來,這就是Executor框架最大的優點。多線程

  • ExecutorService接口介紹

上面咱們講了如何建立一個Executor,可是並無講如何關閉它。既然Executor是爲應用程序服務的,於是他們應該是可關閉的,並將關閉操做中受影響的任務狀態反饋給應用程序。爲了解決執行服務的生命週期問題,ExecutorService擴展了Executor接口,增長了管理生命週期的方法:併發

public interface ExecutorService Extends Executor{
    void shutdown();
    boolean isShutDown();
    boolean isTerminated();
    ……
}

ExecutorService的生命週期分爲三種,運行,關閉和已終止。下面看這個簡單的實例:框架

class NetworkService implements Runnable {
    private final ServerSocket serverSocket;
    private final ExecutorService pool;

    public NetworkService(int port, int poolSize)
        throws IOException {
      serverSocket = new ServerSocket(port);
      pool = Executors.newFixedThreadPool(poolSize);
    }
 
    public void run() { // run the service
      try {
        for (;;) {
          pool.execute(new Handler(serverSocket.accept()));
        }
      } catch (IOException ex) {
        pool.shutdown();
      }
    }
  }

  class Handler implements Runnable {
    private final Socket socket;
    Handler(Socket socket) { this.socket = socket; }
    public void run() {
      // read and service request on socket
    }
 }
  • 第三部分:線程池介紹及使用

  • 幾種類型的線程池

能夠經過Executors類的幾個靜態方法來建立線程池。包含如下幾類線程池:
newFixedThreadPool建立固定長度的線程池。每提交一個任務時就新建一個線程,直到達到線程池的最大數量。異步

newCachedThreadPool建立一個可緩存的線程池,線程的數量不受限制,可是在之前線程可用時將重用他們。socket

newSingleThreadExecutor建立一個使用單個 worker 線程的 ,以無界隊列方式來運行該線程。函數

newScheduleThreadPool建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。相似於一個定時器。工具

  • 設置線程池大小

線程池的理想大小取決於被提交任務的類型以及所部署系統的特性。在代碼中最好不要固定線程池的大小,而要經過某種靈活機制來配置。線程池大小的配置只要避免「過大」和」太小「兩種極端狀況便可。線程池過大會致使大量的線程競爭CPU和內存,最終致使資源耗盡;線程池太小時又會致使資源浪費,因此設置一個合適的線程池大小很是重要。一般有一個簡單的設置線程池大小的公式供咱們參考使用:

N(Threads) = N(cpu) * U(cpu) * (1 + w/c)

N(cpu)表明cpu的個數,U(cpu)表明cpu利用率, w/c表示等待時間與計算時間的比值

  • 配置線程池

ThreadPoolExecutor爲Executor和ExecutorService接口提供了基本實現。下面咱們來看一下ThreadPoolExecutor類的構造函數:該類一共有四個構造函數,其中最基礎的一個構造函數以下:

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

corePoolSize表示的是核心池的大小,第二個參數表示線程池最大的線程數,第三個參數表示存活時間,第四個參數表示給定單元粒度的時間段,第五個參數表示的是工做隊列。

  • 擴展線程池

ThreadPoolExecutor是能夠擴展的,它提供了幾個能夠在子類中改寫的方法:beforeExecutor,afterExecutor,terminated,這些方法可用於擴展ThreadPoolExecutor的行爲。

相關文章
相關標籤/搜索