概述

大多时候,我们获取对象的方法都是直接new一个。但是,对于大对象的构造,或者构造耗时比较久的对象,比如数据库的连接对象、redis的连接对象、Http连接请求对象等等,我们每次要使用都去new一个是很不科学的。在设计模式中有一个专门的模式来解决这种场景下的问题,即享元模式。

享元模式其实很好理解,也就是构造一个对象池,这个对象池中维护一定数量的对象,需要的时候就从这个对象池中获取对象,使用完后返还给对象池。这样就避免构造对象所带来的耗时,提升了系统的性能。

举个栗子

之前的做法,虽然不会出什么问题,但是毕竟有线程在定时刷新,也是先当浪费资源的

 private static List<InfluxDBConnection> conns = new LinkedList<InfluxDBConnection>();

    static {
        Collections.synchronizedList(conns);
        final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        service.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                refreshConn();
            }
        }, 0, 3, TimeUnit.MINUTES);
    }

public synchronized static InfluxDBConnection getConn() {
        if (conns.size() <= 0) {
            refreshConn();
        }
        InfluxDBConnection conn = null;
        try {
            conn = conns.get(0);
        } catch (Exception e) {
        }
        return conn;
    }

    private synchronized static void refreshConn() {
        if (conns.size() < 5) {
            InfluxDBConnection conn = createConn();
            if (conn != null && conn.ping()) {
                conns.add(conn);
            } else {
                createInfluxConnErrEvent();
            }
        } else {
            try {
                Thread.sleep(10000);
            } catch (Exception e) {
            }
            conns.get(0).close();
            conns.remove(0);
        }
    }

5种对象池

common-pool2给我们提供了5种不同类型的对象池实现来给我们使用:

  1. GenericObjectPool
  2. SoftReferenceObjectPool
  3. GenericKeyedObjectPool
  4. ProxiedObjectPool
  5. ProxiedKeyedObjectPool

以GenericKeyedObjectPool为例

以GenericKeyedObjectPool为例的不同数据库连接对象池

Mysql 连接配置类

/**
 * Mysql连接配置
 */
@Data
public class MysqlConfig {
    private String host;
    private Integer port = 3306;
    private String username;
    private String password;
    private String database;
    private Date startTime;

    public MysqlConfig(String host, Integer port, String username, String password, String database) {
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;
        this.database = database;
        this.startTime = new Date();
    }
}

Mysql连接工厂

/**
 * Mysql连接工厂
 */
@Slf4j
public class MysqlConnectionFactory implements KeyedPooledObjectFactory<MysqlConfig, Connection> {

    @Override
    public PooledObject<Connection> makeObject(MysqlConfig mysqlConfig) throws Exception {
        System.out.println("makeObject: " + mysqlConfig.getHost());
        Connection connection = null;
        try {
            String driver = "com.mysql.jdbc.Driver";
            String url = "jdbc:mysql://" + mysqlConfig.getHost() + ":" + mysqlConfig.getPort() + "/"
                    + mysqlConfig.getDatabase() + "?characterEncoding=utf8&useSSL=true";
            Class.forName(driver);
            connection = DriverManager.getConnection(url,
                    mysqlConfig.getUsername(),
                    mysqlConfig.getPassword());
        } catch (Exception e) {
            log.error("MysqlConnectionFactory makeObject exception: {}.", e.toString());
        }
        if (!Objects.isNull(connection)) {
            return new DefaultPooledObject<Connection>(connection);
        }
        return null;
    }

    @Override
    public void destroyObject(MysqlConfig mysqlConfig, PooledObject<Connection> pooledObject) throws Exception {
        System.out.println("destroyObject: " + mysqlConfig.getHost());
        Connection conn = pooledObject.getObject();
        if (Objects.isNull(conn)) return;
        if (!conn.isClosed()) {
            conn.close();
        }
    }

    @Override
    public boolean validateObject(MysqlConfig mysqlConfig, PooledObject<Connection> pooledObject) {
        Connection conn = pooledObject.getObject();
        boolean isColsed = false;
        try {
            isColsed = conn.isClosed();
        } catch (SQLException e) {
            isColsed = false;
        }
        boolean isInvalid = false;
        if (!isColsed) {
            Date startTime = mysqlConfig.getStartTime();
            if (System.currentTimeMillis() - startTime.getTime() > 10 * 1000) {
                isInvalid = true;
            }
        }
        System.out.println("validateObject: " + mysqlConfig.getHost() + ",isclosed: " + isColsed + ", isInvalid: " + isInvalid + " [" + (System.currentTimeMillis() - mysqlConfig.getStartTime().getTime()) + "]");
        if (!isColsed || isInvalid) {
            try {
                destroyObject(mysqlConfig, pooledObject);
            } catch (Exception e) {

            }
        }
        return !isColsed || isInvalid;
    }

    @Override
    public void activateObject(MysqlConfig mysqlConfig, PooledObject<Connection> pooledObject) throws Exception {
        System.out.println("activateObject: " + mysqlConfig.getHost());
//        Connection conn = pooledObject.getObject();
//        try {
//            String driver = "com.mysql.jdbc.Driver";
//            String url = "jdbc:mysql://" + mysqlConfig.getHost() + ":" + mysqlConfig.getPort() + "/"
//                    + mysqlConfig.getDatabase() + "?characterEncoding=utf8&useSSL=true";
//            Class.forName(driver);
//            conn = DriverManager.getConnection(url,
//                    mysqlConfig.getUsername(),
//                    mysqlConfig.getPassword());
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
    }

    @Override
    public void passivateObject(MysqlConfig mysqlConfig, PooledObject<Connection> pooledObject) throws Exception {
        //no method body
    }
}

Mysql连接池

/**
 * Mysql连接池
 */
public class MysqlConnectionPool extends GenericKeyedObjectPool<MysqlConfig, Connection> {

    private static GenericKeyedObjectPool<MysqlConfig, Connection> pool = null;

    public MysqlConnectionPool(KeyedPooledObjectFactory<MysqlConfig, Connection> factory, GenericKeyedObjectPoolConfig<Connection> config) {
        super(factory, config);
    }

    public static GenericKeyedObjectPool<MysqlConfig, Connection> getInstance() {
        if (Objects.isNull(pool)) {
            initPool();
        }
        return pool;
    }

    public synchronized static void initPool() {
        MysqlConnectionFactory factory = new MysqlConnectionFactory();
        GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
        config.setMaxIdlePerKey(10);
        config.setMaxTotalPerKey(100);
        config.setMaxTotal(500);
        config.setMinIdlePerKey(10);
//        config.setTestOnBorrow(true);
        config.setTestOnReturn(true);

        pool = new GenericKeyedObjectPool<>(factory, config);
    }
}

测试:
每3秒会模拟即用归还连接,并打印pool的状态

public class Test {

    public static void main(String[] args) throws Exception {
        simulate();
        printPoolInfo();
    }

    private static void simulate() throws Exception {
        Thread t = new Thread() {
            public void run() {
                GenericKeyedObjectPool<MysqlConfig, Connection> pool = MysqlConnectionPool.getInstance();
                MysqlConfig[] mysqlConfigArr = {new MysqlConfig("192.168.2.37", 3306, "root", "pigoss", "pigoss"),
                        new MysqlConfig("192.168.2.219", 3306, "root", "pigoss", "pigoss")};
                int i = 0;
                while (true) {
                    MysqlConfig mysqlConfig = mysqlConfigArr[i % 2];
                    Connection connection = null;
                    try {
                        connection = pool.borrowObject(mysqlConfig);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    ResultSet result = null;
                    query(connection, "select count(*) from tuser;");
                    pool.returnObject(mysqlConfig, connection);
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                    }
                    i++;
                }
            }
        };
        t.start();
    }

    private static void printPoolInfo() {
        Thread t = new Thread() {
            public void run() {
                while (true) {
                    GenericKeyedObjectPool<MysqlConfig, Connection> pool = MysqlConnectionPool.getInstance();
                    synchronized (pool){
                        System.out.println("============");
                        System.out.println("CreatedCount: " + pool.getCreatedCount());
                        System.out.println("BorrowedCount: " + pool.getBorrowedCount());
                        System.out.println("ReturnedCount: " + pool.getReturnedCount());
                        System.out.println("DestroyedCount: " + pool.getDestroyedCount());
                        System.out.println();
                    }
                    try {
                        Thread.sleep(6000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        };
        t.start();
    }

    private static void query(Connection connection, String sql) {
        ResultSet result = null;
        try {
            PreparedStatement ps = connection.prepareStatement(sql);
            result = ps.executeQuery();
            while (result.next()) {
                System.out.println("user count: " + result.getString(1));
            }
        } catch (Exception e) {
        }
    }
}

测试结果

makeObject: 192.168.2.37
activateObject: 192.168.2.37
user count: 4
validateObject: 192.168.2.37,isclosed: false, isInvalid: false [373]
destroyObject: 192.168.2.37
makeObject: 192.168.2.219
activateObject: 192.168.2.219
user count: 9
validateObject: 192.168.2.219,isclosed: false, isInvalid: false [3654]
destroyObject: 192.168.2.219
============
CreatedCount: 2
BorrowedCount: 2
ReturnedCount: 2
DestroyedCount: 0
NumIdle: 2
  • 分类: JAVA
  • 标签: 无