多線程系列(2):線程池

簡介:

線程相對於進程屬於輕量級,雖然線程是輕量級的,合理的使用線程池的好處有

  • 降低資源的消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗
  • 提高響應速度。當任務到達時,任務可以不需要等到線程池創建就能立即執行
  • 提高線程的可管理性。線程是稀缺資源,如果無限制的創建不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配,調優和監控。但是要做到合理利用線程池,必須對其實現原理了如指掌。

一:常用類

實際工作中很少顯式的去創建線程,因爲如果顯式創建線程當併發很大時會使得內存耗盡(每個線程都需要分配內存空間),實際開發中一般使用線程池來控制線程的最大個數以及合理重複的利用線程

Exception in thread “main” java.lang.OutOfMemoryError: unable to create new native thread

public static void main(String[] args) {
	// 短時間內創建大量線程會使內存耗盡
    for (int i = 0; i < 3000; i++) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

這裏寫圖片描述

Executors
合理配置:
CPU密集(計算):線程數和CPU核數相等
IO密集(讀寫): 2倍的CPU核數

public class Executors {
	// 線程工廠
	public static ThreadFactory defaultThreadFactory();

	// 創建固定數量的線程池,如果沒有任務,那麼線程一直等待, n一般與cpu個數一致(Runtime.getRuntime().availableProcessors())
	// 最爲常用 
	public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
	// 創建一個可緩存的線程池
	public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
	// 創建只有一個線程的線程池,所有提交的線程是順序執行的
	public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
	// 創建一個支持定時及週期性的任務執行的線程池,多數情況下可用來替代Timer類
	public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
}


```java
public interface ExecutorService extends Executor {
	// 提交可運行任務供執行,並返回表示該任務的未來。未來的get方法將在成功完成後返回
	Future<?> submit(Runnable task);
	<T> Future<T> submit(Runnable task, T result);
	<T> Future<T> submit(Callable<T> task);
	
	// 停止線程池接收新的線程,並且執行完已經接收的線程,執行完畢後結束掉線程池(如果不結束掉可以看到main方法會一直運行不會結束,生成上一般都是保持一直運行不需要關閉的)
	void shutdown();
}

public interface ThreadFactory {
	// 將Runnable包裝成Thread
    Thread newThread(Runnable r);
}

Callable 的 call()方法只能通過 ExecutorService 的 submit(Callable task) 方法來執行,並且返回一個 Future,是表示任務等待完成的 Future。

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

submit首先選擇空閒線程來執行任務,如果沒有,纔會創建新的線程來執行任務

ExecutorService 的 shutdown()方法來平滑地關閉 ExecutorService,調用該方法後,將導致 ExecutorService 停止接受任何新的任務且等待已經提交的任務執行完成(已經提交的任務會分兩類:一類是已經在執行的,另一類是還沒有開始執行的),當所有已經提交的任務執行完畢後將會關閉 ExecutorService。因此我們一般用該接口來實現和管理多線程。

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

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

二:Runnable示例

ThreadFactory

public static void main(String[] args) {
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        Thread thread = threadFactory.newThread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t i=" + i);
            }
        });
        thread.start();
    }

newCachedThreadPool

public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 緩存線程池創建的個數是去不確定的,根據每個線程的總執行時間確定的,當一個線程執行完後會有60秒的存活時間
        // 如果有新的任務需要執行就使用剛纔執行完空閒的線程執行,如果沒有空閒的線程則新啓動一個線程,最多啓動Integer.MAX_VALUE個線程
        for (int i = 0; i < 500; i++) {
            int finalI = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t  " + "i=" + finalI);
                }
            });
        }
}

這裏寫圖片描述
這裏開啓了500個任務,這裏測試的緩衝池中有0~48共49個線程,說明其它任務共用了線程池中的線程

newFixedThreadPool

public class ThreadPoolTest {
    public static void main(String[] args) {
        // 固定線程池,線程池中的個數是固定的值
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                System.out.println(Thread.currentThread().getName());
            });
            executorService.execute(thread);
        }
        executorService.shutdown();
    }
}

這裏寫圖片描述
我的機器Runtime.getRuntime().availableProcessors()=4,所以有4個線程

public static void main(String[] args) {
   ScheduledExecutorService executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            // 不會立即執行,5秒鐘後執行
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\tloop i=" + i);
            executorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t  " + "i=" + finalI);
                }
            }, 5, TimeUnit.SECONDS);
        }
}

這裏寫圖片描述

public static void main(String[] args) {
	 // 所有任務只使用一個線程在執行
     ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            scheduledExecutorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t  " + "i=" + finalI);
                }
            });
        }

        scheduledExecutorService.shutdown();  
}

這裏寫圖片描述

三:Callable示例

public class ThreadPoolTest {
    public static void main(String[] args) {
        List<Future<String>> futureList = new ArrayList<>();

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            Future<String> future = executorService.submit(new MyCallable(i));
            futureList.add(future);
        }

        futureList.forEach(future -> {
            while (!future.isDone()) { }
            try {
                String result = future.get();
                System.out.println(result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                // 執行以前提交的任務,但不接受新任務
                executorService.shutdown();
            }
        });
    }
}

class MyCallable implements Callable<String> {
    private int i;
    public MyCallable(int i){
        this.i = i;
    }

    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName() + "\t" + "i=" + i;
    }
}

這裏寫圖片描述


四:自定義線程池

前面四種創建線程池的方式很多使用的是ThreadPoolExecutor來創建的,我們也可以使用ThreadPoolExecutor自定義自己的線程池

ThreadPoolExecutor

public class ThreadPoolExecutor extends AbstractExecutorService {
	/*
	* 
	* @param corePoolSize 核心線程池: 線程池中所保存的核心線程數,包括空閒線程
	* @param maximumPoolSize 最大線程池 池中允許的最大線程數。
	* @param keepAliveTime 線程池中的空閒線程所能持續的最長時間
	* @param unit keepAliveTime對應的單位
	* @param workQueue 任務執行前保存任務的隊列,僅保存由 execute方法提交的Runnable任務
	*
	*/
	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory);
    
    /*
    * 當創建一個線程池的時候,該線程池會包含兩個線程,一個是核心線程池,一個是最大線程池。
	* 當提交一個任務的時候,如果核心線程池沒有滿的情況下就在覈心線程池中執行
	* 如果核心線程池已經滿了,就把該線程放到隊列中,如果核心線程池有空閒的線程就從隊列中取出來執行
	* 當隊列滿的情況下(每種隊列都會設置最大的容量),就把線程放到最大線程池中去執行
	* 當最大線程池也已經滿的情況下就走拒絕策略
    */                          
	public void execute(Runnable command) {
        if (command == null) throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
}

這裏寫圖片描述

public static void main(String[] args) {
    // 等待隊列
    BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(20);
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 500, TimeUnit.MICROSECONDS, blockingQueue);
    for (int i = 0; i < 10; i++) {
        threadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "\t running...");
            try { Thread.sleep(500); } catch (InterruptedException e) { }
        });
    }
    threadPool.shutdown();
}

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