1. 程式人生 > >(十七)java併發程式設計--任務執行之執行緒池的使用

(十七)java併發程式設計--任務執行之執行緒池的使用

大多數併發程式圍繞著”任務執行”來構造的: 任務通常是一些抽象的且離散的工作單元。通過把一個用程式的共工作分解到多個任務中,可以簡化程式的組織結構,提供一種自然的事務邊界來優化錯誤恢復過程,以及提供一種自然的工作結構來提升併發性。

1 線上程中執行任務

前面的部落格中主要說的執行緒本身,但是我們如何管理和合理使用這些執行緒呢?
當圍繞著“任務執行”來設計應用程式結構時,第一步要找到清晰的任務邊界。在理想情況下,各個任務之間是互相獨立的:任務並不依賴於其他任務的狀態、結果或邊界效應。
大多數伺服器應用提供了一種自然的任務邊界選擇方式:以獨立的請求為邊界。WEb伺服器、郵件伺服器、檔案伺服器、EJB伺服器以及資料庫伺服器等,這些伺服器都能通過網路接受遠端客戶的連線請求。將獨立的請求作為邊界,既可以實現任務的獨立性,又可以實現合理的任務規模。

2 序列執行任務

執行緒最簡單的策略就是單個執行緒中序列地執行各項任務。如下,SingleThreadWebServer將會序列地處理它的任務。

package Executor;

import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by fang on 2017/12/9.
 */
public class SingleThreadWebServer {
    public static void main(String[] args) throws Exception {
        ServerSocket socket = new
ServerSocket(80); while (true){ Socket connection = socket.accept(); handleRequest(connection); } } private static void handleRequest(Socket connection) { //... } }

主執行緒在接受連線與處理相關請求等操作之間不斷的交替執行。當伺服器正在處理請求時,新到來的連線必須等待直到處理完成,然後伺服器
再次呼叫accept。
這裡面,web請求處理中包含一組不同運算與io操作,服務其必須處理套接字(socket)IO以讀取請求和寫回響應,這些操作通常會由於網路阻塞或連通性問題被阻塞。此外伺服器還可能處理檔案IO或者資料庫請求,這些操作同樣會阻塞。
伺服器的資源利用率非常低,因為單執行緒在等待IO操作完成時,cpu將處於空閒狀態。

2.1 顯示的為任務建立執行緒

那為每一個請求建立一個新的執行緒來提供服務,從而實現高響應性,如下ThreadPerTaskWebServer程式碼所示。

package Executor;

import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by fang on 2017/12/9.
 * 建立多個執行緒.
 */
public class ThreadPerTaskWebServer {
    public static void main(String[] args) throws Exception {
        ServerSocket socket = new ServerSocket(80);
        while (true){
            final Socket connection = socket.accept();
            Runnable task = new Runnable() {
                public void run() {
                    handleRequest(connection);
                }


            }
            new Thread().start();

        }
    }

    private static void handleRequest(Socket connection) {
        //...
    }

}

對單執行緒的改造後,主執行緒將建立一個新執行緒來處理請求,而不是在主迴圈中進行。由此可得出如下結論:
1)任務處理過程從主執行緒中分離出來,可使主迴圈能夠更快的等待下一個到來的連線。這使得程式在完成前面的請求之前可以接受新的請求,從而提高響應性。
2)任務可以並行處理,從而能同時服務多個請求。如果有多個處理器,或者任務由於某種原因被阻塞、例如等待IO完成、獲取鎖或者資源的可能性,程式的吞吐率將提高。
3)任務處理程式碼必須是執行緒安全的,當有多個任務時,會併發的呼叫這段程式碼。

“採用為每個任務開啟一個執行緒”的方法能提升序列執行的效能。只要請求的到達速率不超出服務的請求處理能力,那麼這種方法可以同時帶來更快的響應性和吞吐率。

2.2 無限制建立執行緒的不足

如果如限制的建立執行緒還有如下問題
1)執行緒生命週期開銷非常高。
2)資源消耗。
3)穩定性。

3 Executor框架

基於上述兩種方式的問題(單執行緒和無線多執行緒的缺點),我們可以通過有界佇列來防止高負荷的應用耗盡記憶體。而執行緒池則簡化了執行緒的管理工作,jdk5中新增了java.util.concurrent 包提供了一種靈活執行緒池實現作為Executor框架的一部分。在java類中,任務的執行的主要抽象不是Thread,而是Executor,如下程式碼。

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

Executor 簡單的介面卻為非同步任務執行框架提供了基礎,該框架能支撐多種不同型別的任務執行策略。它提供了一種標準方法將任務的提交過程與執行過程解耦開來,並用Runnable來表示任務。Executor的實現還提供了生命週期的支援,以及統計資訊收集、應用程式管理機制和效能監視機制。

Executor 基於一種生產者–消費者模式,提交任務的操作相當於生產者,執行任務的執行緒則相當於消費者。

3.1基於Executor的web伺服器

基於Executor來構建web伺服器是非常容易的。如下所示,定義了一個固定長度的執行緒池。

package Executor;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Created by fang on 2017/12/9.
 * 固定長度的執行緒池.
 */
public class TaskExecutionWebServer {
    private static final int NTHREADS = 100;

    private static final Executor exec  = Executors.newFixedThreadPool(NTHREADS);

    public static void main(String[] args) throws IOException{
        ServerSocket serverSocket = new ServerSocket();
        while (true){
            final Socket connection = serverSocket.accept();
            Runnable task = new Runnable() {
                public void run() {
                    handleRequest(connection);
                }


            };

            exec.execute(task);
        }
    }

    private static void handleRequest(Socket connection) {
    }
}

通過使用Executor將請求處理任務的提交與任務的實際執行解耦開來。通常,Executor配置是一次性的,因此部署階段可以完成,而提交任務的程式碼會不斷的擴充套件整個程式中,增加了修改的難度。

可以為每個請求啟動一個新執行緒的Executor。

public class ThreadPerTaskExecutor implements Executor{
    public void execute(Runnable r){
        new Thread(r).start();
    }
}

可以類似單執行緒的行為,以同步方式執行每個任務。

public class WithinThreadExecutor implements Executor{
    pulic void execute(Runnable r){
        r.run();
    }
}

以上三種方式只是作為基於Executor的web伺服器的一個可能搭建情況。

3.2執行策略

將任務提交和執行解耦開來,可以為某任務指定和修改執行策略。在執行策略中包括了“what 、where、When、How”
等方面,包括:
1)在什麼(what)執行緒中執行任務?
2)任務按照什麼(what)順序執行(FIFO、FIFO、優先順序)?
3)有多少個(how many)任務能併發執行?
4)在佇列中有多少個(how many)任務在等待執行?
5)如果系統由於過載而需要絕句一個任務,那麼應該選擇(which)哪個任務?另外,如何通(how)知應用程式有任務被拒絕?
6)在執行一個任務之前或之後,應該進行哪些(what)動作?

最佳策略取決於可用的計算機資源以及對服務質量的需求,通過限制併發數量可以確保程式不會因為資源耗盡而失敗,或者由於在稀缺資源上發生競爭而嚴重影響效能。通過任務的提交與任務的執行分離開來,有助於在部署階段選擇與硬體資源最匹配的執行策略。

當我們看到下面這種形式的程式碼時:
new Thread(runnable).start()
並且你希望獲得一種更靈活的執行策略時,請考慮使用Executor來代替Thread。

3.3 執行緒池

執行緒池,從字面意義上來看,是管理一組同構工作執行緒的資源池.
執行緒池之工作佇列:儲存了所有等待執行的任務。
執行緒池之工作者執行緒:從工作佇列中獲取一個執行緒,然後返回執行緒池並等待下一個任務。
java類庫提供了靈活的執行緒池以及預設配置,可以通過呼叫Executors中的靜態工廠方法之一來建立一個執行緒池。

3.3.1 newFixedThreadPool

固定執行緒池的長度,每提交一個任務就建立一個執行緒,直到達執行緒池的最大長度,這時執行緒池的規模將不會再變化(如果某個執行緒由於發生了未預期的Exception而結束那麼執行緒池將會補充一個執行緒)。
程式碼如下:

package Executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by fang on 2017/12/10.
 * 建立一個定長的執行緒池,可控制併發的最大數,超出執行緒會在佇列中等待,程式碼如下.
 * 定長執行緒池的大小最好根據系統資源設定,如Runtime.getRuntime().availableProcessors()cpu處理器數.
 */
public class ThreadPoolExecutorDemo2 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for(int i = 0;i<10;i++){
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(index);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

3.3.2 newCachedThreadPool

建立一個可快取的執行緒池,如果執行緒池的當前規模超過了處理需求時,*那麼將會回收空閒的執行緒,*而當需求增加時,則新增新的執行緒,執行緒的規模不存在
任何的限制。程式碼如下:

package Executor;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by fang on 2017/12/10.
 * 是使用可快取的執行緒池,如果需要處理的任務大於執行緒池的長度,可靈活回收執行緒,
 * 若無可回收,則建立新的執行緒.
 *
 * 因為執行緒池無限大,所以當執行第二個任務的時候第一個任務已經執行完成,會複用第一個任務的執行緒,而不用每次都新建執行緒.
 */
public class ThreadPoolExecutorDemo1 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for(int i = 0;i<10;i++){
            final int index = i;
            Thread.sleep(index*1000);
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(index);
                }
            });
        }
    }
}

3.3.3 newSingleThreadExecutor

是一個單執行緒的Executor,它建立單個執行緒來執行任務如果這個執行緒異常結束,會建立另外一個執行緒來替代。newSingleThreadExecutor能確保依照任務在佇列中順序
來序列執行(如FIFO、FIFO、優先順序)。

package Executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by fang on 2017/12/10.
 * 建立單執行緒化的執行緒池,只會用唯一的工作執行緒來執行,保證任務是按照(FIFO\LIFO,優先順序來執行的)
 */
public class ThreadPoolExecutorDemo4 {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for(int i=0;i<10;i++){
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                public void run() {
                    System.out.println(index);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

}

3.3.1 newScheduledThreadPool

newScheduledThreadPool建立了一個固定長度的執行緒池,而且以延遲或者定時的方式來執行任務,類似Timer。

“從單個執行緒序列執行”到“為每個任務分配一個執行緒”,變成基於執行緒池的策略,將對應用程式的穩定性產生重大的 影響:Web伺服器不會再在高負載情況下執行失敗。
由於伺服器不會建立千萬個執行緒來爭奪有限的CPU和記憶體資源,因此伺服器的效能將平緩的降低。
通過Executor,可以實現各種調優、管理、監視、記錄日誌、錯誤報告和其他功能,如果不使用任務的執行框架,增加這些功能是十分困難的。

package Executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Created by fang on 2017/12/10.
 * 建立一個定長的執行緒池,支援定時以及週期性任務執行。程式碼如下。
 *
 */
public class ThreadPoolExecutorDemo3 {
    public static void main(String[] args) throws InterruptedException {
        //延期3s執行
        ScheduledExecutorService scheduledThreadPool1  = Executors.newScheduledThreadPool(5);
        for(int i = 0;i<10;i++){
            scheduledThreadPool1.schedule(new Runnable() {
                public void run() {
                    System.out.println("delay 3 seconds");
                }
            },3,TimeUnit.SECONDS);
        }

        //表示延遲1s後每3s執行一次.
        ScheduledExecutorService scheduledThreadPool2 = Executors.newScheduledThreadPool(5);
        scheduledThreadPool2.scheduleAtFixedRate(new Runnable() {
            public void run() {
                System.out.println("delay 1 seconds, and excute every 3 seconds");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
}

3.4 Executor的生命週期

可以建立一個Executor,但如何關閉Executor?Executor建立執行緒來執行任務。但JVM只有在所有(非守護)執行緒全部終止後才會退出(Executor所有執行緒自行完畢後退出)
因此,無法正確的關閉Executor,JVM則將無法結束。
Executor擴充套件了ExecutorService介面,添加了一些用於宣告週期的管理辦法(還有一些其他用於任務提交的便利方法),如下程式碼。

/**
 * Created by fang on 2017/12/10.
 * Executor 生命週期管理辦法.
 */
public interface ExecutorService {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination (long timeout, TimeUnit unit) throws InterruptedException;

    //.....一些其他用於任務提交的便利方法.
}

shutdown():執行平緩的關閉過程,不再接受新任務,同時等待已經提交的任務執行完成(包括未開始執行的任務)。
shutdownNow():執行粗暴的關閉過程,嘗試取消所有執行中的任務,並且不再啟動佇列中尚未開始的任務。

awaitTermination:等待ExecutorServer終止結束狀態。通常在呼叫awaitTermination後會立即呼叫shutdown,從而產生同步的關閉ExecutorService效果。
isTerminated:輪詢Executor是否已經結束。
如下程式碼所示。

package Executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Created by fang on 2017/12/10.
 * 停止執行緒.
 */
public class ExecutorServiceExample {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        for(int i =0;i<100;i++){
            executorService.submit(new NewTask());
        }

        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
    }
}

class NewTask implements Runnable{
    public void run() {

    }
}

3.4 設定執行緒池的大小

看了《編髮程式設計實戰》中以及併發程式設計網中的部分,整理如下。
一般來說,大家認為執行緒池的大小經驗值應該是這樣設定:
(1)如果是CPU密集型執行緒池的大小設定為N+1
(2)如果是IO密集型,則執行緒池的大小設定為2N+1
在IO優化中,這樣的估算公式可能會更合適:
最佳執行緒數目 =( (執行緒等待時間 +執行緒CPU時間)/執行緒cpu時間) *cpu數目
顯然,執行緒等待時間越長需要的執行緒數越多, 執行緒CPU時間所佔比例越高, 需要執行緒越少. IO等待–>需要的執行緒多 CPU等待高–>需要的匯流排程會少
例如:
平均每個執行緒CPU時間0.5s, 而執行緒等待時間1.5s, CPU核數8, 根據公式得到((0.5+1.5)/0.5)*8 = 32
轉化公式:
最佳執行緒數 = (執行緒等待時間與cpu時間比 + 1) * CPU數目

3.5 管理佇列任務

用於儲存等待執行的任務阻塞佇列,可以選擇以下阻塞佇列。
ArrayBlockingQueue:一個基於陣列結構的有界阻塞佇列,此佇列按照(先進先出FIFO)原則對元素進行排序。
LinkedBlockingQueue一個基於連結串列結構的阻塞佇列,也是按照FIFO原則對元素排序。吞吐量高於ArrayBlockingQueue,靜態工廠方法Executors.newFixedThreadPool()使用了這個佇列。

SynchronousQueue一個不儲存元素的阻塞佇列,每個插入必須要等待到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態,吞吐量要高於Linked-BlokingQueue,靜態工廠方法Executors.newCachedThreadpool使用了這個佇列。

PriorityBlockingQueue:一個具有優先順序的無限阻塞佇列。

3.6任務飽和策略

當有界佇列被填滿後,說明任務佇列處於飽和狀態,必須採用一種策略處理提交的新任務。這個策略預設情況下是AbortPolicy,表示無法處理新任務
丟擲異常。在jdk1.5中提供了4中飽和策略。
1. AbortPolicy:終止策略,直接丟擲異常。
2. CallerRunsPolicy:只用呼叫者所在的執行緒來執行任務。
3. DiscardOldestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務。
4. DiscardPolicy:不處理,丟棄掉

4 攜帶結果的任務Callable和Future

之前的文章介紹過Callable和future,下面是callable future 和執行緒的方式來實現,如下程式碼。

package Executor;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

/**
 * Created by fang on 2017/12/10.
 * callable future ExecutorService example
 */
public class MyCallable implements Callable<String>{

    public String call() throws Exception {
        Thread.sleep(1000);

        return Thread.currentThread().getName();
    }

    public static void main(String[] args) {
        //get ExecutorService form Executors unility class ,thread pool is10
        ExecutorService executor = Executors.newFixedThreadPool(10);
        //create a list to hold the Future object associated with Callabel;
        List<Future<String>> futurelist = new ArrayList<Future<String>>();

        //create MyCallable instance
        Callable<String> callable = new MyCallable();
        for(int i=0;i<100;i++){
            //submit Callable tasks to be executed by thread pool
            Future<String> future = executor.submit(callable);
            //add future to list ,we can get return value using future .
            futurelist.add(future);
        }

        for(Future<String> future:futurelist){
            try {
                //print the return value of future ,notice the output delay in console
                //because Future.get() waits for task to get completed.
                System.out.println(new Date() + "::" + future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        //shut down the execuutor service now
        executor.shutdown();
    }
}

輸出結果如下:
Sun Dec 10 16:48:25 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:26 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:27 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:28 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:29 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:30 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:31 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:32 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:33 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-10
Sun Dec 10 16:48:34 CST 2017::pool-1-thread-2
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-3
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-1
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-5
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-4
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-8
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-7
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-9
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-6
Sun Dec 10 16:48:35 CST 2017::pool-1-thread-10
從輸出結果可以看到,執行緒池中建立了10個執行緒,執行緒池中的執行緒執行完畢就會釋放,然後來了新的執行緒再繼續建立。

小思:上篇中還在思考怎樣效率最高,和錄入自己的大腦中,今天有了一個辦法,就是和其他人討論問題,或者講給其他人,你能給別人講明白了,就是自己真的懂了。和別人在討論過程中,就會更加深刻,第二點是把知識轉化為圖,大腦對圖的印象要深刻與文字。
第三點帶著問題去看書或者看視訊,我可以看一下多執行緒相關的面試題,從書中找到答案,者需以上三種方式可以能提高些效率吧..

上篇大部分來自於《java併發程式設計》一是感覺這本書真的挺好的,二是,貌似可能“吃的不夠多”,還不能自己消化了,可以吸收一些,
需要反覆的反芻。