前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家java】Java中Future模式衍生出来的高级应用---自己手写一个数据库连接池

【小家java】Java中Future模式衍生出来的高级应用---自己手写一个数据库连接池

作者头像
YourBatman
发布2019-09-03 16:47:20
8590
发布2019-09-03 16:47:20
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

说在前面

阅读本文之前,请确保你已经了解了Java中Future模式。若没有,ring出门左转,参考博文:【小家java】一个例子让就能你彻底理解Java的Future模式,Future类的设计思想

手动写一个数据库连接池

要求:该连接池能够复用数据库连接,并且能在高并发情况下正常工作

代码语言:javascript
复制
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了。

比如我们现在有如下一个场景:

  • 某一时刻,同时有3个线程进入getConnection方法,调用pool.containsKey(key)都返回false,然后3个线程各自都创建了连接。虽然ConcurrentHashMap的put方法只会加入其中一个,但还是生成了2个多余的连接。如果是真正的数据库连接,那会造成极大的资源浪费。

所以我们的问题来了,为了减少资源的浪费,需要解决如何在多线程访问getConnection方法时,只执行一次createConnection。

结合之前Future模式的实现分析:当3个线程都要创建连接的时候,如果只有一个线程执行createConnection方法创建一个连接,其它2个线程只需要用这个连接就行了。再延伸,把createConnection方法放到一个Callable的call方法里面,然后生成FutureTask。我们只需要让一个线程执行FutureTask的run方法,其它的线程只执行get方法就好了。

啥都不说了,上代码:

代码语言:javascript
复制
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

代码语言:javascript
复制
 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作为中间转换,成功实现了让某个方法只被一个线程执行。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年08月21日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 说在前面
  • 手动写一个数据库连接池
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档