阅读本文之前,请确保你已经了解了Java中Future模式。若没有,ring出门左转,参考博文:【小家java】一个例子让就能你彻底理解Java的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,当多个线程同时调用getConnection方法时,性能大幅提升。
难道就这么简单?那你就too young too simple了。
比如我们现在有如下一个场景:
所以我们的问题来了,为了减少资源的浪费,需要解决如何在多线程访问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,而不是新的值。这点各位自己看看源码就一目了然了,有时候是需要注意返回值的
咱们模拟这上面的情况,推演一遍:
在并发的环境下,通过FutureTask作为中间转换,成功实现了让某个方法只被一个线程执行。