阅读完需:约 4 分钟
在学习了Future模式后可以来尝试着应用它,所有就有了这个数据库连接池。
1、手动写一个数据库连接池
要求:该连接池能够复用数据库连接
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,当多个线程同时调用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,但木有关系
Callable<Connection> callable = new Callable<Connection>() {
@Override
public Connection call() throws Exception {
return createConnection();
}
};
FutureTask<Connection> newTask = new FutureTask<Connection>(callable);
//因为使用的ConcurrentHashMap,所以这里最终只会put成功有一个线程的数据。当put成功之后会返回v。如果返回的v是null,
connectionTask = pool.putIfAbsent(key, newTask);
if (connectionTask == null) {
connectionTask = newTask;
connectionTask.run();
}
return connectionTask.get();
}
}
public Connection createConnection() {
return new Connection();
}
class Connection {
}
}
有必要解释下putIfAbsent():如果指定的键未与某个值关联(或映射到null),则将其与给定值关联并返回null,否则返回当前值。 简单的若,如果key不存在就把值放进去并且返回null,如果key存在,就不放进去并且返回v
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
System.out.println(map.put("a","a")); // null
System.out.println(map.putIfAbsent("b","b")); //null
System.out.println(map.putIfAbsent("b","c")); //b 注意此处返回值为b而不是c
//put的用法 和putIfAbsent有些许区别
System.out.println(map.put("c","c")); //null
System.out.println(map.put("d","d")); //null
System.out.println(map.put("c","d")); //c 注意此处返回值为c而不是d
}
由此可见,map的put/putIfAbsent成功后的返回值,返回的是oldValue,而不是新的值。这点各位自己看看源码就一目了然了,有时候是需要注意返回值的。
咱们模拟这上面的情况,推演一遍:
- 当3个线程同时进入else语句块时,各自都创建了一个FutureTask,但是ConcurrentHashMap只会加入其中一个。第一个线程执行pool.putIfAbsent方法后返回null,然后connectionTask被赋值,接着就执行run方法去创建连接,最后get。后面的线程执行pool.putIfAbsent方法不会返回null,就只会执行get方法。
在并发的环境下,通过FutureTask作为中间转换,成功实现了让某个方法只被一个线程执行。