1. 程式人生 > >併發程式設計(一):執行緒基礎、執行緒之間的共享與協作

併發程式設計(一):執行緒基礎、執行緒之間的共享與協作

一、基礎概念

1.1 CPU核心數、執行緒數

**兩者的關係:**cpu的核心數與執行緒數是1:1的關係,例如一個8核的cpu,支援8個執行緒同時執行。但在intel引入超執行緒技術以後,cpu與執行緒數的關係就變成了1:2。此外在開發過程中並沒感覺到執行緒的限制,那是因為cpu時間片輪轉機制(RR排程)的演算法的作用。什麼是cpu時間片輪轉機制看下面1.2.

1.2 CPU時間片輪轉機制

**含義就是:**cpu給每個程序分配一個“時間段”,這個時間段就叫做這個程序的“時間片”,這個時間片就是這個程序允許執行的時間,如果當這個程序的時間片段結束,作業系統就會把分配給這個程序的cpu剝奪,分配給另外一個程序。如果程序在時間片還沒結束的情況下阻塞了,或者說程序跑完了,cpu就會進行切換。cpu在兩個程序之間的切換稱為“上下文切換”,上下文切換是需要時間的,大約需要花費5000~20000(5毫秒到20毫秒,這個花費的時間是由作業系統決定)個時鐘週期,儘管我們平時感覺不到。所以在開發過程中要注意上下文切換(兩個程序之間的切換)對我們程式效能的影響。

1.3 什麼是程序和執行緒

程序:它是屬於程式排程/執行的資源分配的最小單位,一個程序的內部可能有多個執行緒,多個執行緒之間會共享這個程序的資源。程序與程序直線是相互獨立的
執行緒:它是cpu排程的最小單位,執行緒本身是不能獨立進行的,它必須依附某個程序,執行緒本身是不擁有系統資源的。

1.4 什麼是並行和併發

並行:例如一個飯堂有八個視窗,也就是說,同一時刻可以有8個人進行打飯,那麼也就說這個飯堂的並行度是8
併發:它嚴格說起來是要與一個時間單位相關的,簡單來說就是一個時間段內可以同時處理的事情數。例如這個飯堂有8個視窗,每個人打飯需要花費30秒鐘,那麼一分鐘內(8個視窗 * 60秒 / 每人打飯需要30秒 = 16)這個飯堂的併發度就是16
總結:並行是同一時刻可以處理多少件事,併發是在單位時間內可以處理多少件事情。

1.5 高併發程式設計的意義、好處和注意事項

通過以上1.1~1.4的瞭解,我們可以知道高併發程式設計可以充分利用cpu的資源,例如一個8核的cpu跑一個單線的程式,那麼意味著在任意時刻,我有7個CPU核心是浪費掉的。另外可以充分地加快使用者的響應時間。同時使用併發程式設計可以使我們的程式碼模組化、非同步化。
注意事項/難點: 有與執行緒之間會共享程序的資源,既然說是共享資源,就有可能存在衝突。在高併發程式設計中如果控制不好,還有可能會造成執行緒的死鎖(什麼是死鎖在我之後的文章會詳細講解,讀者也可自行百度)。每啟動一個執行緒,作業系統就需要為這個執行緒分配一定的資源,執行緒數太多還可能會把內容消耗完畢,會導致系統宕機。

二、java中執行緒基礎

2.1 啟動和終止執行緒

啟動執行緒的方式:
① 類Thread
② 介面Runnable(推薦使用這種,因為介面可以多實現)
③ 介面Callable:與Runnable的區別是,實現Runnabble接口裡的run方法是沒有返回值的,而Callable是允許有返回值的

package javax.test;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @auther xiehuaxin
 * @create 2018-08-24 10:56
 * @todo
 */
public class Test {

    private static class RunnableThread implements Runnable {
        @Override
        public void run() {
            System.out.println("實現Runnable方式建立執行緒");
        }
    }

    private static class CallableThread implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "this is return result";
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        RunnableThread runnableThread = new RunnableThread();
        //要啟動實現Runnablede的執行緒的話還需要把runnable的例項傳到Thread裡
        new Thread(runnableThread).start();

        CallableThread callableThread = new CallableThread();
        //由於new Thread只接受Runnable型別的構造引數,所以要先把Callable包裝一下
        FutureTask<String> futureTask = new FutureTask<>(callableThread);
        new Thread(futureTask).start();
        //獲取返回值,get方法是阻塞的
        System.out.println(futureTask.get());
    }
}

輸出:

this is return result
實現Runnable方式建立執行緒

執行緒結束的方法:
① 方法執行完自動終止
② 丟擲異常,又沒有捕獲異常

③ 早期還提出過三個方法終止執行緒stop(), resume(), suspend() ,這三個方法都不建議用於終止執行緒
原因:一旦呼叫stop會強行終止執行緒,無法保證執行緒的資源正常釋放。suspend()呼叫後執行緒是不會釋放資源的,很容易引起死鎖。

正確的終止執行緒的方法:interrupt(),isinterrupted()以及靜態方法interrupted().
三者的區別:
① interrupt():是屬於Thread類的方法,作用終止一個執行緒,但並不是強行關閉一個執行緒(java的執行緒是協作式的,不是強迫式的,呼叫一個執行緒的interrupt()方法並不會強制關閉一個執行緒,它就好比其他執行緒對要關閉的執行緒打了一聲招呼,告訴被關閉執行緒它要中斷了,但被關閉執行緒什麼時候關閉完全由它自身做主),執行緒呼叫該方法並不會立刻終止。既然呼叫interrupt()方法起不到終止執行緒的目的,那麼它為什麼要這樣設計?這樣設計時為了讓每個執行緒有充分的時間去做執行緒的清理工作。進行開發的時候要對執行緒的中斷請求進行處理,否則就不是一個設計良好的併發程式。總的來說,它的目的只是把執行緒中的“中斷標誌位”置為true
② isInterrupted(),判定當前執行緒是否處於中斷狀態。通過這個方法判斷中斷標誌位是否為true。
③ static方法isInterrupted(), 也是判斷當前執行緒是否處於中斷狀態。當呼叫此方法時,它會把中斷標誌位改為false。

2.2 執行緒再認識

① 當執行緒呼叫了wait(),join(),sleep()方法時,方法會丟擲InterruptedException,這個時候執行緒的中斷標誌會被複位成為false,所以這個時候我們應該在catch裡面再呼叫一次interrupt(),再次中斷一次。

package javax.test;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @auther xiehuaxin
 * @create 2018-08-24 10:56
 * @todo 如何安全地中斷執行緒
 */
public class Test{

    private static class UseThread extends Thread {
        public UseThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            //如果現在這個while的條件不是“!isInterrupted()”而是“true”,那麼即使main方法裡呼叫了test.interrupt()還是無法終止執行緒的,這就是java協作式。
            while (!isInterrupted()) {
                System.out.println("Thread " + Thread.currentThread().getName() + " is running.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    interrupt();//當執行緒呼叫了wait(),join(),sleep()方法時,方法會丟擲InterruptedException,這個時候執行緒的中斷標誌會被複位成為false,所以這個時候我們應該在catch裡面再呼叫一次interrupt(),再次中斷一次。
                }
            }
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        UseThread test = new UseThread("myThread");
        test.start();
        //讓main執行緒等待
        Thread.sleep(500);
        test.interrupt();

    }
}

如果是通過實現Runnable介面建立的執行緒,那麼怎麼正確地停止執行緒呢?因為interrupt()方法和isInterrupted()方法都是屬於Thread類的,這個時候我們就要通過Thread.currentThread()來呼叫了,如下

  private static class UseRunnable implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("Thread " + Thread.currentThread().getName() + " is running.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();//當執行緒呼叫了wait(),join(),sleep()方法時,方法會丟擲InterruptedException,這個時候執行緒的中斷標誌會被複位成為false,所以這個時候我們應該在catch裡面再呼叫一次interrupt(),再次中斷一次。
                }
            }
        }
    }

另外,有的人喜歡在類裡定義一個boolean的標誌來中斷執行緒。這種方式一般情況下能工作,但有些情況又不能工作、譬如線上程內呼叫了一個阻塞的方法(sleep\wait…),那麼執行緒會一直阻塞在那個地方,這個時候即使你改變了自己定義的標誌,它也不會去判斷while的條件的,一定要執行緒從阻塞中被喚醒、並且執行完阻塞後的程式碼後才會回到while的判斷,這就造成了執行緒在處理響應時非常不及時。

② 開發過程中也可為執行緒設定優先順序,執行緒的優先順序的範圍為1~10,預設值為5,優先順序較高的執行緒獲得分配的時間片就較高。呼叫Thread.setPriority()方法進行設定。這個優先順序的設定在不同的作業系統中會有不一樣的結果,有些系統設定會忽略這個優先順序的設定,有的作業系統可能全部給你設定為5,所以在程式開發過程中, 不能指望這個操作。

③ 守護執行緒:守護執行緒的執行緒和主執行緒是同時銷燬的,主執行緒退出了,守護程序就一定會結束。設定守護程序是通過Thread.setDaemon(true)進行設定的,而且需要在呼叫start()方法之前設定。使用守護執行緒需要注意:守護程序裡非try..finally是不能保證一定執行finally的。

④ volatile是最輕量級的保證同步的方法,但它一般只使用於一個執行緒寫,多個執行緒讀這種場景。

⑤ run()和start()的區別:呼叫run方法其實就像普通的類呼叫類中的方法一樣,run()方法由誰呼叫就歸宿與哪個執行緒。只有呼叫start()方法才是真正的啟動執行緒。

這裡寫圖片描述
⑥ 就緒狀態也成為可執行狀態,呼叫了一個執行緒的start()方法,形成就處於可執行狀態,但這個時候並不是執行狀態,只有當cpu排程到該執行緒才是執行狀態。

⑦ yield()方法的作用是,當一個執行緒執行完了,就把cpu的時間讓出來。那麼它與sleep()方法的區別呢?呼叫sleep()方法,在sleep()的時間段內,cpu是不會再排程該執行緒,但是呼叫了yield()方法的下一時刻,cpu還是有可能會排程到該執行緒的

2.3 執行緒間的共享

synchronized(內建鎖),要麼載入方法上面,要麼是用作同步塊的形式來使用,最大的作用是確保在多個執行緒在同一時刻只能有一個執行緒處於方法或同步塊之中,這樣它就保證了執行緒對變數訪問的可見性與排差性。 鎖的是物件,不是程式碼塊,每個物件在記憶體的物件頭上有一個標誌位,標誌位上有1~2個位元組標誌它為一個鎖,synchronized的作用就是當所有的執行緒去搶這個物件的標誌位,誰把這個標誌位指向了自己,那就認為這個執行緒搶到了這個鎖。

物件鎖和類鎖:java的物件鎖和類鎖在鎖的概念上基本上和內建鎖是一致的,但是,兩個鎖實際是有很大的區別的,物件鎖是用於物件例項方法,或者一個物件例項上的,類鎖是用於類的靜態方法或者一個類的class物件上的。我們知道,類的物件例項可以有很多個,但是每個類只有一個class物件,所以不同物件例項的物件鎖是互不干擾的,但是每個類只有一個類鎖。但是有一點必須注意的是,其實類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定例項方法和靜態方法的區別的。

在WEB框架開發過程中,常使用到spring框架,在spring框架裡面沒有做特殊設定的話,sping為我們建立的物件預設是隻有一個的,所以這個時候可以保證鎖住的是同一個物件,但是如果在配置中,sping執行new出多個物件,這個時候加鎖的時候就要考慮鎖的是不是同一個物件了。

volatile是虛擬機器提供的最輕量的同步機制(因為它的讀取和寫入並沒有做synchronized處理),聲明瞭該變數為volatile的話,就是告訴虛擬機器,每次要用該變數時,總是要在主記憶體中讀取。volatile並不是執行緒安全的,只能保證變數的可見性,不能保證原子性,例如:age = age+20,虛擬機器不能保證你這個操作時原子的。

ThreadLoacl(執行緒變數):可以確保每個執行緒只使用自己那一部分的東西。例如一個變數使用ThreadLocal包裝的話,那麼每個執行緒都是使用自己的那一份變數的拷貝。可以理解為Map

package javax.test;

/**
 * @auther xiehuaxin
 * @create 2018-08-26 14:40
 * @todo
 */
public class UseThreadLoacal {

/**
 *初始ThreadLocal
 *底層用map實現
 *這裡的Thread存放的是Integer型別的資料,假如說要用來存放一個超級龐大的資料,由於每個執行緒都有一個自己的副本,這樣對記憶體的資源的佔用是相當大的,所以ThreadLocal儘量用來存放比較小的資料。
 */
    static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };
    public void startThreadArray() {
        Thread[] threads = new Thread[3];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new TestThread(i));
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
    }

    public static class TestThread implements Runnable{
        int id;
        public TestThread(int id) {
            this.id = id;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ":start");
            Integer s = threadLocal.get();//獲取變數的值
            s = s + id;
            threadLocal.set(s);//把值寫回去
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
        }
    }

    public static void main(String[] args) {
        UseThreadLoacal useThreadLoacal = new UseThreadLoacal();
        useThreadLoacal.startThreadArray();
    }
}

2.4 執行緒間的協作

2.4.1 Thread.join:假如執行緒A呼叫了執行緒B的join方法,那麼執行緒A必須等到執行緒B執行完成才能繼續執行。

package javax.test;

/**
 * @auther xiehuaxin
 * @create 2018-08-26 15:49
 * @todo
 */
public class UseJoin {

    private static class JumpQuery implements Runnable{
        private Thread thread;//用來插隊的執行緒
        public JumpQuery (Thread thread) {
            this.thread = thread;
        }
        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " terminted.");
        }
    }

    public static void main(String[] args) {
        Thread previous = Thread.currentThread();//初始時,這個是主執行緒
        for(int i = 0; i < 10; i++) {
            //i=0是,previous是主執行緒,i=1時,previous是i=0這個執行緒
            Thread thread = new Thread(new JumpQuery(previous),String.valueOf(i));
            System.out.println(previous.getName() + "jump a queue the thread:" + thread.getName());
            thread.start();
            previous = thread;
        }
        try {
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + " termined.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

輸出:

mainjump a queue the thread:0
0jump a queue the thread:1
1jump a queue the thread:2
2jump a queue the thread:3
3jump a queue the thread:4
4jump a queue the thread:5
5jump a queue the thread:6
6jump a queue the thread:7
7jump a queue the thread:8
8jump a queue the thread:9
main termined.
0 terminted.
1 terminted.
2 terminted.
3 terminted.
4 terminted.
5 terminted.
6 terminted.
7 terminted.
8 terminted.
9 terminted.

② 呼叫yield()方法和sleep()方法以後,持有的鎖是不釋放的,所以一般呼叫這兩個方法的良好寫法是不要寫在synchronized程式碼塊外面。
③ 呼叫wait()方法和notify()方法是會釋放鎖的,呼叫這兩個方法的前提是必須持有鎖,而且呼叫這兩個方法之後,還是會把這兩個方法所在的synchronized程式碼塊中的程式碼執行完成才釋放鎖(呼叫這兩個方法是不會釋放鎖的),所以良好的寫法是寫在synchronized程式碼塊中的最後一行。
④ wait()、notify()、notifyAll()方法是和物件繫結一起的,話句話來說,就是你在object1上呼叫了notifyAll方法,那麼通知的就是在object1上等待的執行緒,並不能通知到object2物件上的執行緒。

2.4.2 等待和通知

輪詢無法保證及時性,資源的開銷也比較大,大部分時間都在做無用功。為了解決這種情況,java裡提供了一種等待和通知機制,當執行緒呼叫wait方法會進入等待狀態,當呼叫notify或notifyAll(首選notifyAll,因為notify通知的是等待佇列中的一個執行緒,有可能發生訊號丟失的情況。看如下程式碼,把Express.java中的notifyAll方法改成notify觀察輸出結果)方法就會喚醒執行緒。wait、notify、notifyAll這三個方法是物件本身的方法,並不是執行緒的方法。

package com.cvc.nelson;

/**
 * @auther xiehuaxin
 * @create 2018-08-28 17:25
 * @todo
 */
public class Express {

    public static final String CITY = "ShangHai";
    //快遞運輸的里程數
    private int km;
    //快遞到達的地點
    private String site;

    public Express() {
    }
    public Express(int km, String site) {
        this.km = km;
        this.site = site;
    }

    /**
     * 公里數發生變化,然後通知處於wait狀態並需要處理公里數的執行緒進行業務處理
     */
    public synchronized void changeKm() {
        this.km = 101;
        notify();
    }

    /**
     * 地點發生變化,然後通知處於wait狀態並需要處理公里數的執行緒進行業務處理
     * 標準正規化的第一步是等待和通知都要先拿到鎖,所以加個synchronized
     */
    public synchronized void changeSite() {
        this.site = "BeiJing";
        notify();
    }


    public synchronized void waitKm() {
        //當里程數小於100時就繼續等待
        while (this.km <= 100) {
            try {
                wait();
                System.out.println("check km thread["+Thread.currentThread().getId()+"] is be notified");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("km做自己的業務.........");
    }

    public synchronized void waitSite() {
        while (CITY.equals(this.site)) {
            try {
                wait();
                System.out.println("check site thread["+Thread.currentThread().getId()+"] is be notified");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("site做自己的業務.........");
    }
}
package com.cvc.nelson;

/**
 * @auther xiehuaxin
 * @create 2018-08-28 18:00
 * @todo
 */
public class TestWaitAndNotifyAll {
    private static Express express = new Express(0,Express.CITY);

    private static class CheckKm extends Thread {
        @Override
        public void run() {
            express.waitKm();
        }
    }

    private static class CheckSite extends Thread {
        @Override
        public void run() {
            express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i=0; i<3; i++) {
            new CheckSite().start();
        }
        for (int i = 0; i < 3; i++) {
            new CheckKm().start();
        }

        Thread.sleep(1000);
        express.changeKm();//快遞地點發生變化
    }
}

線上程之間進行通訊,往往有一個”等待和通知的標準正規化”,如下:
呼叫wait的執行緒(等待方):
① 獲取物件的鎖
② 在一個迴圈裡判定條件是否滿足,不滿足就呼叫wait方法
③ 條件滿足就執行業務邏輯
通知方:
① 獲取物件的鎖
② 改變條件
③ 通知所有等待在物件的線

===================================
等待超時模式
如上程式碼,假如執行緒沒有被喚醒的話,將會無限期等待下去,但是現實中我們是不允許這種情況存在的,一般是等待某一時間以後,如果拿到了結果就返回,如果沒拿到結果依然要返回,返回之後根據沒有拿到結果這種情形做另外一種處理。
假設等待時長為T,那超時的時間截點就是(當前時間+T)
虛擬碼如下:
定義一個超時時間long overTime = now + T;
long remain = T;//等待的持續時間
while(result不滿足條件 && remain>0) {
wait(remain);
//
remain = overTime - now;//等待還剩下的持續時間
}
return result;
“等待超時模式”實現一個連線池

package com.xiangxue.ch1.pool;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;

import com.xiangxue.tools.SleepTools;

/**
 *@author Mark老師   享學課堂 https://enjoy.ke.qq.com 
 *
 *類說明:實現了資料庫連線的實現
 */
public class SqlConnectImpl implements Connection{

    /*拿一個數據庫連線*/
    public static final Connection fetchConnection(){
        return new SqlConnectImpl();
    }
}
package com.xiangxue.ch1.pool;

import java.sql.Connection;
import java.util.LinkedList;

/**
 *@author Mark老師   享學課堂 https://enjoy.ke.qq.com 
 *
 *類說明:實現一個數據庫的連線池
 */
public class DBPool {

    //資料庫池的容器
    private static LinkedList<Connection> pool = new LinkedList<>();

    public DBPool(int initalSize) {
        if(initalSize>0) {
            for(int i=0;i<initalSize;i++) {
                pool.addLast(SqlConnectImpl.fetchConnection());
            }
        }
    }

    //在mills時間內還拿不到資料庫連線,返回一個null
    public Connection fetchConn(long mills) throws InterruptedException {
        synchronized (pool) {
            if (mills<0) {
                while(pool.isEmpty()) {
                    pool.wait();
                }
                return pool.removeFirst();
            }else {
                long overtime = System.currentTimeMillis()+mills;
                long remain = mills;
                while(pool.isEmpty()&&remain>0) {
                    pool.wait(remain);
                    remain = overtime - System.currentTimeMillis();
                }
                Connection result  = null;
                if(!pool.isEmpty()) {
                    result = pool.removeFirst();
                }
                return result;
            }
        }
    }

    //放回資料庫連線
    public void releaseConn(Connection conn) {
        if(conn!=null) {
            synchronized (pool) {
                pool.addLast(conn);
                pool.notifyAll();
            }
        }
    }


}
package com.xiangxue.ch1.pool;

import java.sql.Connection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *@author Mark老師   享學課堂 https://enjoy.ke.qq.com 
 *
 *類說明:
 */
public class DBPoolTest {
    static DBPool pool  = new DBPool(10);
    // 控制器:控制main執行緒將會等待所有Woker結束後才能繼續執行
    static CountDownLatch end;

    public static void main(String[] args) throws Exception {
        // 執行緒數量
        int threadCount = 50;
        end = new CountDownLatch(threadCount);
        int count = 20;//每個執行緒的操作次數
        AtomicInteger got = new AtomicInteger();//計數器:統計可以拿到連線的執行緒
        AtomicInteger notGot = new AtomicInteger();//計數器:統計沒有拿到連線的執行緒
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new Worker(count, got, notGot), 
                    "worker_"+i);
            thread.start();
        }
        end.await();// main執行緒在此處等待
        System.out.println("總共嘗試了: " + (threadCount * count));
        System.out.println("拿到連線的次數:  " + got);
        System.out.println("沒能連線的次數: " + notGot);
    }

    static class Worker implements Runnable {
        int           count;
        AtomicInteger got;
        AtomicInteger notGot;

        public Worker(int count, AtomicInteger got,
                               AtomicInteger notGot) {
            this.count = count;
            this.got = got;
            this.notGot = notGot;
        }

        public void run() {
            while (count > 0) {
                try {
                    // 從執行緒池中獲取連線,如果1000ms內無法獲取到,將會返回null
                    // 分別統計連接獲取的數量got和未獲取到的數量notGot
                    Connection connection = pool.fetchConn(1000);
                    if (connection != null) {
                        try {
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            pool.releaseConn(connection);
                            got.incrementAndGet();
                        }
                    } else {
                        notGot.incrementAndGet();
                        System.out.println(Thread.currentThread().getName()
                                +"等待超時!");
                    }
                } catch (Exception ex) {
                } finally {
                    count--;
                }
            }
            end.countDown();
        }
    }
}

========================================================

其他補充知識:

①如何通過java程式碼的形式

package javax.test;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

/**
 * @auther xiehuaxin
 * @create 2018-08-24 10:35
 * @todo
 */
public class Test {
    public static void main(String[] args) {
        //jdk提供的虛擬機器執行緒管理的介面ThreadMXBean,一般除了對系統進行監控,其他情況基本用不著這個介面
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        /**
         * 檢視當前系統有哪些執行緒
         */
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,false);
        for(ThreadInfo info : threadInfos) {
            System.out.println("[" + info.getThreadId() + "] " + info.getThreadName());
        }
    }
}

輸出:

[6] Monitor Ctrl-Break
[5] Attach Listener//負責獲取當前程式執行的各種資訊,包括記憶體印象,執行緒的棧,系統屬性等等
[4] Signal Dispatcher//它專門向虛擬機器分發處理訊號的
[3] Finalizer//呼叫物件的final方法的執行緒
[2] Reference Handler//用來負責清除引用的執行緒
[1] main//主執行緒

推薦書籍:《java併發程式設計實戰》