使用Future模式優化一個簡單的數據庫連接池

個人博客原文主頁:
使用Future模式優化一個簡單的數據庫連接池


我們自己寫一個簡單的數據庫連接池,能夠複用數據庫連接,並且能在高併發情況下正常工作

package test;

import java.util.concurrent.ConcurrentHashMap;

public class ConnectionPool {

    private ConcurrentHashMap<String, Connection> pool = new ConcurrentHashMap<String, Connection>();
    
    public Connection getConnection(String key) {
        Connection conn = null;
        if (pool.containsKey(key)) {
            conn = pool.get(key);
        } else {
            conn = createConnection();
            pool.putIfAbsent(key, conn);
        }
        return conn;
    }
    
    public Connection createConnection() {
        return new Connection();
    }
    
    class Connection {}
}

我們用了ConcurrentHashMap,這樣就不必把getConnection方法置爲synchronized(當然也可以用Lock),當多個線程同時調用getConnection方法時,性能大幅提升。

貌似很完美了,但是有可能導致多餘連接的創建,推演一遍:

某一時刻,同時有3個線程進入getConnection方法,調用pool.containsKey(key)都返回false,然後3個線程各自都創建了連接。雖然ConcurrentHashMap的put方法只會加入其中一個,但還是生成了2個多餘的連接。如果是真正的數據庫連接,那會造成極大的資源浪費。

所以,我們現在的難點是:如何在多線程訪問getConnection方法時,只執行一次createConnection。

結合之前Future模式的實現分析:當3個線程都要創建連接的時候,如果只有一個線程執行createConnection方法創建一個連接,其它2個線程只需要用這個連接就行了。再延伸,把createConnection方法放到一個Callable的call方法裏面,然後生成FutureTask。我們只需要讓一個線程執行FutureTask的run方法,其它的線程只執行get方法就好了。

上代碼:

package test;

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

public class ConnectionPool {

    private ConcurrentHashMap<String, FutureTask<Connection>> pool = new ConcurrentHashMap<String, FutureTask<Connection>>();

    public Connection getConnection(String key) throws InterruptedException, ExecutionException {
        FutureTask<Connection> connectionTask = pool.get(key);
        if (connectionTask != null) {
            return connectionTask.get();
        } else {
            Callable<Connection> callable = new Callable<Connection>() {
                @Override
                public Connection call() throws Exception {
                    return createConnection();
                }
            };
            FutureTask<Connection> newTask = new FutureTask<Connection>(callable);
            connectionTask = pool.putIfAbsent(key, newTask);
            if (connectionTask == null) {
                connectionTask = newTask;
                connectionTask.run();
            }
            return connectionTask.get();
        }
    }

    public Connection createConnection() {
        return new Connection();
    }

    class Connection {
    }
}

推演一遍:當3個線程同時進入else語句塊時,各自都創建了一個FutureTask,但是ConcurrentHashMap只會加入其中一個。第一個線程執行pool.putIfAbsent方法後返回null,然後connectionTask被賦值,接着就執行run方法去創建連接,最後get。後面的線程執行pool.putIfAbsent方法不會返回null,就只會執行get方法。

在併發的環境下,通過FutureTask作爲中間轉換,成功實現了讓某個方法只被一個線程執行。


拓展,FutureTask的get()方法是關鍵,下面附上關鍵的代碼

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}
private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        else if (q == null)
            q = new WaitNode();
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);
    }
}

get方法的邏輯很簡單,如果call方法的執行過程已完成,就把結果給出去;如果未完成,就將當前線程掛起等待。awaitDone方法裏面死循環的邏輯,推演幾遍就能弄懂;它裏面掛起線程的主要創新是定義了WaitNode類,來將多個等待線程組織成隊列


下面再列出Future模式中關聯到的基本的接口和類:

public class FutureTask<V> implements RunnableFuture<V> {
	/** The underlying callable; nulled out after running */
    private Callable<V> callable;
}
public interface RunnableFuture<V> extends Runnable, Future<V>
public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}
  • FutureTask實現了RunnableFuture接口,同時包含一個Callable實例
  • RunnableFuture繼承了兩個頂級接口Future和Runnable
  • 歸根結底,Future、Runnable、Callable三個頂級接口決定了FutureTask的功用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章