天天看點

為什麼要用線程池?

線程池的基本思想

  • 線程池是一種多線程處理形式,處理過程中将任務添加到隊列,然後在建立線程後自動啟動這些任務。
  • 在系統中開辟一塊區域,存放一些待命的線程,這個區域成為線程池。
  • 如果有需要執行的任務,則從線程池中借一個待命的線程來執行指定的任務,任務執行結束在将借的線程歸還,這樣就避免了大量建立線程對象,浪費CPU、記憶體資源的問題。

合理利用線程池能夠帶來三個好處

  • 第一:降低資源消耗。通過重複利用已建立的線程降低線程建立和銷毀造成的消耗。
  • 第二:提高響應速度。當任務到達時,任務可以不需要的等到線程建立就能立即執行。
  • 第三:提高線程的可管理性。線程是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的配置設定,調優和監控。

繼承關系圖

為什麼要用線程池?

常見線程池

  1. java.util.concurrent.Executors#newSingleThreadExecutor()
單線程線程池,即線程池中每次隻有一個線程工作,單線程串行執行任務(适用于需要異步執行但需要保證任務順序的場景)
  1. java.util.concurrent.Executors#newFixedThreadPool(int)
固定大小線程池,每送出一個任務就是一個線程,直到達到線程池的最大數量,然後後面進入等待隊列直到前面的任務完成才繼續執行
  1. java.util.concurrent.Executors#newCachedThreadPool()
可緩存線程池(Cached線程池),建立一個線程池,該線程池根據需要建立新線程,但在以前構造的線程可用時将重用它們。超過60秒未使用的線程将被終止并從緩存中移除,當有任務來時,又智能的添加新線程來執行。
  1. java.util.concurrent.Executors#newScheduledThreadPool(int)
Scheduled線程池,适用于定期執行任務場景,支援按固定頻率定期執行和按固定延時定期執行兩種方式(延遲線程池,Queue隊列使用了DelayedWorkQueue,這是一個可延時執行阻塞任務的隊列)
定期執行線程池(Scheduled線程池)
1、第一次執行任務延遲initialDelay
2、從第二次執行任務開始,每隔period執行一次
3、若任務執行時間t>period,則任務每隔t執行一次
4、設定定時方法:scheduleAtFixedRate(Runnable command, long initialDelay,long period,TimeUnit unit)
           

5 工作竊取線程池,使用的ForkJoinPool,是固定并行度的多任務隊列,适合任務執行時長不均勻的場景

建立任務線程類

class MyTask implements Runnable{
	private int count;
	private String taskName;
	public MyTask(String taskName, int count){
		this.count = count;
		this.taskName = taskName;
	}
	public void run(){
		System.out.println("\n"+Thread.currentThread().getName()+"開始執行任務"+taskName+">>");
		for(int i=0;i<count;i++){
			System.out.print(taskName+"-"+i+" ");
		}
		System.out.println("\n"+taskName+"任務執行結束。。");
	}
}
           

單任務線程池的使用

public class Main{
	
	public static void main(String[] args){
         MyTask task1 = new MyTask("task1", 20);
         MyTask task2 = new MyTask("task2", 20);
         MyTask task3 = new MyTask("task3", 10);
         //建立單任務線程池
         ExecutorService singlePool = Executors.newSingleThreadExecutor();
         singlePool.execute(task1);
         singlePool.execute(task2);
         singlePool.execute(task3);
         //所有任務都結束後關閉線程池
         singlePool.shutdown();
	}
}

           

固定大小的線程池

public class Main{
	
	public static void main(String[] args){
         MyTask task1 = new MyTask("task1", 50);
         MyTask task2 = new MyTask("task2", 50);
         MyTask task3 = new MyTask("task3", 30);
         //建立固定大小的線程池
         ExecutorService threadPool = Executors.newFixedThreadPool(2);
         threadPool.execute(task1);
         threadPool.execute(task2);
         threadPool.execute(task3);
         //所有任務都結束後關閉線程池
         threadPool.shutdown();
	}
}

           

建立可緩存線程池【推薦使用】

public static void main(String[] args){
     MyTask task1 = new MyTask("task1", 30);
     MyTask task2 = new MyTask("task2", 30);
     MyTask task3 = new MyTask("task3", 20);
     //建立大小可變的線程池
     ExecutorService threadPool = Executors.newCachedThreadPool();
     threadPool.execute(task1);
     threadPool.execute(task2);
     threadPool.execute(task3);
     //所有任務都結束後關閉線程池
     threadPool.shutdown();
}

           

建立延遲線程池

public static void main(String[] args){
     MyTask task1 = new MyTask("task1", 30);
     MyTask task2 = new MyTask("task2", 30);
     MyTask task3 = new MyTask("task3", 20);
     //建立有時延的線程池
     ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
     //建立單線程延時線程池
     ScheduledExecutorService singleThreadPool = Executors.newSingleThreadScheduledExecutor();
     
     threadPool.schedule(task1, 1, TimeUnit.SECONDS);
     threadPool.schedule(task2, 1500, TimeUnit.MILLISECONDS);
     singleThreadPool.schedule(task3, 2000, TimeUnit.MILLISECONDS);
     
     //所有任務都結束後關閉線程池
     threadPool.shutdown();
     singleThreadPool.shutdown();
}

           
  • ScheduledExecutorService類的scheduleAtFixedRate(Runnable, long, long, TimeUnit)方法源碼如下
/**
 * Creates and executes a periodic action that becomes enabled first
 * after the given initial delay, and subsequently with the given
 * period; that is executions will commence after
 * <tt>initialDelay</tt> then <tt>initialDelay+period</tt>, then
 * <tt>initialDelay + 2 * period</tt>, and so on.
 */
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit);
           
  • 項目實戰代碼
private ThreadPoolExecutor executor = new ThreadPoolExecutor(80, 100, 60L, TimeUnit.SECONDS,
		new ArrayBlockingQueue(100));

private ScheduledExecutorService monitorService = Executors.newScheduledThreadPool(1);

@Override
public void init() throws ServletException {
	final SimpleDateFormat formate = new SimpleDateFormat("HH:mm:ss");
	// 循環任務,按照上一次任務的發起時間計算下一次任務的開始時間
	monitorService.scheduleAtFixedRate(new Runnable() {
		@Override
		public void run() {
			
		}
	}, 10, 10, TimeUnit.SECONDS);
}
           

自定義參數的線程池

使用java.util.concurrent.ThreadPoolExecutor類(實作了ExecutorService)來實作自定義的線程池,當有新任務到達時,按照以下規則處理:

1)如果目前線程池中的線程數量比規定标準值少,則傾向于建立線程;

2)如果目前線程池中的線程數量比規定标準值多,則傾向于把新的任務放到隊列中;如果隊列已滿,并且線程數量沒有超過最大值,則建立新線程。

3)如果目前線程池中的線程數量已達最大值,且隊列已滿,則請求被拒絕。

4)如果空閑線程超過預設的存活時間,則将空閑線程對象銷毀。

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

ThreadPoolExecutor類一些常用的方法:

public int getCorePoolSize() 擷取線程池的标準大小

public int getActiveCount() 傳回線程池中正在執行任務的線程數量

public int getPoolSize() 擷取線程池的目前大小

public int getMaximumPoolSize() 擷取線程池的最大大小

public BlockingQueue getQueue() 傳回線程池的工作等待隊列

public static void main(String[] args){
       MyTask task1 = new MyTask("task1", 30);
        MyTask task2 = new MyTask("task2", 30);
        MyTask task3 = new MyTask("task3", 20);
        MyTask task4 = new MyTask("task4", 20);
        //建立工作等待隊列
        BlockingQueue workQueue = new ArrayBlockingQueue(3);
        //建立自定義線程池
        ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
       		 2, 4, 100, TimeUnit.SECONDS, workQueue);
        myThreadPool.execute(task1);
        myThreadPool.execute(task2);
        myThreadPool.execute(task3);
        myThreadPool.execute(task4);
        //所有任務都結束後關閉線程池
        myThreadPool.shutdown();
}
           

開發規約

  1. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式建立線程。

    說明:使用線程池的好處是減少在建立和銷毀線程上所消耗的時間以及系統資源的開銷,解決

    資源不足的問題。如果不使用線程池,有可能造成系統建立大量同類線程而導緻消耗完記憶體或

    者“過度切換”的問題。

  2. 【強制】線程池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣

    的處理方式讓寫的同學更加明确線程池的運作規則,規避資源耗盡的風險。

    說明:Executors 傳回的線程池對象的弊端如下:

1)FixedThreadPool 和 SingleThreadPool:

允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,進而導緻 OOM。

2)CachedThreadPool 和 ScheduledThreadPool:

允許的建立線程數量為 Integer.MAX_VALUE,可能會建立大量的線程,進而導緻 OOM。

相關文檔

  • JAVA自定義線程池
  • 線程池工作原理,任務拒接政策有哪幾種

繼續閱讀