zoukankan      html  css  js  c++  java
  • 序列键生成器及单例多例模式

      有时候我们希望生成全局唯一的序列号。可以用于生成主键或者生成全局的序列号用于生成编号或者其他。这时候我们可以用SQL语句自行管理键值。使用一个表来存储所有的键列值。如下表所示:

    key  value
    PO_NUMBER 105
    SE_NUMBER 2555
    ... ...

    1.  存储方式:

      预定式键值存储:在预定一个值时首先将值更新为下一个可用值,然后查出来之后提供给客户端使用。这样万一出现中断的话顶多是浪费这几个键值。每次可以预定多个键值(也就是一个键值区间)而不是一个值,也就是更新键值的时候将键值增加大于1的数目。这么做可以避免多次访问数据库。

      记录式键值存储:也就是说,键值首先被返还给客户端,然后记录到数据库中取。这样做的缺点是:一旦系统出现中断,就可能出现客户端已经使用了一个键值,而这个键值却没有来得及存储到数据库中。在系统重启之后,系统还会从这个已经被使用过的键值开始,从而导致错误。

    2.单例模式与多例模式的应用

    单例模式:

      可以用单例模式来实现,整个系统只有一个序列键值管理器来管理序列号。

    多例模式:

      多例类往往持有一个内蕴状态(内蕴状态是存储在享元对象内部并且不会随环境的改变而改变),多例类的每一个实例都有独特的内蕴状态。一个多例类持有一个集合对象,用来登记自身的实例,而其内蕴状态往往就是登记的键值。当客户端通过多例类的静态工厂方法请求多例类的实例时,这个工厂方法都会在集合内查询是否有这样的一个实例。如果有直接返回给客户端;如果没有就创建一个这样的实例,并登记到集合中,然后返回给客户端。如下:

    键名为内蕴状态;键名和自身作为map的key和value存入集合中。

    package cn.qlq.singleton;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    public class KeyGenerator {
    
        // 多例模式应用
        private static ConcurrentHashMap<String, KeyGenerator> keyGenerators = new ConcurrentHashMap<>();
    
        private KeyGenerator(String key) {
            // 用key做处理
        }
    
        /**
         * 静态工厂方法提供自己的实例
         * 
         * @return
         */
        public static KeyGenerator getInstance(String keyName) {
            if (keyGenerators.containsKey(keyName)) {
                return keyGenerators.get(keyName);
            }
    
            KeyGenerator keyGenerator = new KeyGenerator(keyName);
            keyGenerators.put(keyName, keyGenerator);
            return keyGenerator;
        }
    }

    3.  单例模式应用 

    1.没有数据库的情况

      首先不使用数据库,用一个成员属性模拟键值。如下:

    package cn.qlq;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * 单例模式的唯一值生成器
     * 
     * @author QiaoLiQiang
     * @time 2019年6月12日下午10:39:42
     */
    public class KeyGenerator {
    
        /**
         * 存放key、value
         */
        private ConcurrentHashMap<String, Integer> values = new ConcurrentHashMap<>();
    
        private static KeyGenerator keyGenerator = new KeyGenerator();
    
        private KeyGenerator() {
            // 防止反射创建实例
            if (keyGenerator != null) {
                throw new RuntimeException("not allowed!");
            }
        }
    
        /**
         * 静态工厂方法提供自己的实例
         * 
         * @return
         */
        public static KeyGenerator getInstance() {
            return keyGenerator;
        }
    
        /**
         * 获取下一个序列制
         * 
         * @param key
         *            序列的键
         * @return 自增后的值
         */
        public synchronized int getNextKey(String key) {
            // 如果存在就加1且返回
            if (values.containsKey(key)) {
                Integer nextValue = values.get(key) + 1;
                values.put(key, nextValue);
                return nextValue;
            }
    
            // 初始化
            Integer startValue = 1;
            values.put(key, startValue);
            return startValue;
        }
    
    } 

    测试代码:(两个线程使用序列生成器)

    package cn.qlq;
    
    import java.util.concurrent.CountDownLatch;
    
    public class MainClass {
    
        public static void main(String[] args) throws InterruptedException {
            // 制造一个闭锁
            final CountDownLatch countDownLatch = new CountDownLatch(2);
    
            // 模拟使用序列生成器
            for (int i = 0; i < 2; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 4; j++) {
                            String key = j % 2 + "";
                            System.out.println(key + "	" + KeyGenerator.getInstance().getNextKey(key));
                        }
    
                        countDownLatch.countDown();
                    }
                }).start();
            }
    
            // 阻塞
            countDownLatch.await();
        }
    
    }

    结果:

    0 1
    0 2
    1 1
    1 2
    0 3
    0 4
    1 3
    1 4

      上面可以满足基本的使用,但是有一个问题是系统重启之后values所以的数据会重新从0开始,显然不符合要求。所以需要数据库支持。有时候可能会想到简单的存在文件中,但是存到文件中对于集群又不太适用,所以用下面的存库操作。

    2.有数据库的情况

      与上面的一样,只是数据的存储是在数据库中。

    数据库表结构如下:

    代码如下:

    package cn.qlq.singleton;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    import java.sql.SQLException;
    import java.util.LinkedList;
    
    /**
     * 模拟一个简单的连接池并且执行SQL
     * 
     * @author Administrator
     *
     */
    public class JDBCUtils {
    
        private static String JDBC_DRIVER = "com.mysql.jdbc.Driver";
        private static String DB_URL = "jdbc:mysql://localhost:3306/test1";
        private static String USER = "sa";
        private static String PASS = "123456";
        private static LinkedList<Connection> connections = new LinkedList<>();
    
        public static Object executeSQL(String sql, Object... params) {
            Connection connection = null;
            try {
                connection = getConnection();
                PreparedStatement statement = connection.prepareStatement(sql);
    
                // 设置参数(注意JDBC的所有下标从1开始)
                if (params != null && params.length > 0) {
                    for (int i = 0, length_1 = params.length; i < length_1; i++) {
                        Object param = params[i];
                        if (param instanceof String) {
                            statement.setString(i + 1, (String) param);
                        } else if (param instanceof Long || param instanceof Integer) {
                            statement.setLong(i + 1, Long.valueOf(param.toString()));
                        }
                    }
                }
                
                // 查询
                if (sql.contains("select")) {
                    ResultSet result = statement.executeQuery();
    
                    // 所有的列信息(总列数、类型以及名称)
                    /*ResultSetMetaData metaData = result.getMetaData();
                    int columnCount = metaData.getColumnCount();
                    for (int i = 1; i <= columnCount; i++) {
                        String columnName = metaData.getColumnName(i);
                        String columnClassName = metaData.getColumnClassName(i);
                        System.out.println(columnName);
                        System.out.println(columnClassName);
                    }*/
    
                    // 遍历每一行的数据
                    while (result.next()) {
                        return result.getInt(1);
                    }
    
                    return -1;
                }
    
                // 更新
                statement.executeUpdate();
            } catch (Exception e) {
                e.printStackTrace();
                // 记录日志
            } finally {
                if (connection != null) {
                    releaseConnection(connection);
                }
            }
            return null;
        }
    
        private static Connection getConnection() throws ClassNotFoundException, SQLException {
            if (connections.size() == 0) {
                initConnections();
            }
    
            return connections.removeFirst();
        }
    
        private static void releaseConnection(Connection connection) {
            connections.add(connection);
        }
    
        private static void initConnections() {
            try {
                Class.forName(JDBC_DRIVER);
                for (int i = 0; i < 5; i++) {
                    Connection conn = (Connection) DriverManager.getConnection(DB_URL, USER, PASS);
                    conn.setAutoCommit(true);
                    connections.add(conn);
                }
            } catch (Exception e) {
                // 记录日志
            }
        }
    
    }
    package cn.qlq.singleton;
    
    public class KeyGenerator {
    
        private static String QUERY_SQL = "select idValue from ids where idType = ?";
    
        private static String UPDATE_SQL = "update ids set idValue = idValue + 1 where idType = ?";
    
        private static String INSERT_SQL = "insert into ids(idValue,idType) values(?,?)";
    
        private static KeyGenerator keyGenerator = new KeyGenerator();
    
        private KeyGenerator() {
            // 防止反射创建实例
            if (keyGenerator != null) {
                throw new RuntimeException("not allowed!");
            }
        }
    
        /**
         * 静态工厂方法提供自己的实例
         * 
         * @return
         */
        public static KeyGenerator getInstance() {
            return keyGenerator;
        }
    
        /**
         * 获取下一个序列制
         * 
         * @param key
         *            序列的键
         * @return 自增后的值
         */
        public synchronized int getNextKey(String key) {
            // 如果存在就加1且返回
            Object result = JDBCUtils.executeSQL(QUERY_SQL, key);
            if (result != null && (Integer) result > -1) {
                Integer nextValue = (Integer) result + 1;
                JDBCUtils.executeSQL(UPDATE_SQL, key);
                return nextValue;
            }
    
            // 初始化
            Integer startValue = 1;
            JDBCUtils.executeSQL(INSERT_SQL, startValue, key);
            return startValue;
        }
    
    }

    测试代码:

    package cn.qlq.singleton;
    
    import java.util.concurrent.CountDownLatch;
    
    public class MainClass {
    
        public static void main(String[] args) throws InterruptedException {
            // 制造一个闭锁
            final CountDownLatch countDownLatch = new CountDownLatch(2);
    
            // 模拟使用序列生成器
            for (int i = 0; i < 2; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 4; j++) {
                            String key = "测试序列号";
                            System.out.println(key + "	" + KeyGenerator.getInstance().getNextKey(key));
                        }
    
                        countDownLatch.countDown();
                    }
                }).start();
            }
    
            // 阻塞
            countDownLatch.await();
        }
    
    }

     

    3.键值的缓存方案

      上面的操作每一次都进行数据库的访问与更新操作,太频繁了。不如每次从数据库取的时候多取出来一些值,并缓存起来。这么做可以减少数据库的频繁操作。

      与上面方案不同的是每次idValue不是自增1,变成一个区间,比如自增20。为了缓存所有与键有关的信息,特地引入一个KeyInfo类。

      KeyInfo采用多例模式。一个类型对应一个KeyInfo。

    如下:从数据库取出一定的值缓存起来。

    package cn.qlq.singleton;
    
    public class KeyInfo {
    
        // 最大值
        private long maxKey;
    
        // 最小值
        private long minKey;
    
        // 下个值
        private long nextKey;
    
        // 池子大小
        private int poolSize;
    
        // 类型
        private String idType;
    
        public KeyInfo(int poolSize, String idType) {
            this.poolSize = poolSize;
            this.idType = idType;
        }
    
        /**
         * 从数据库取值并重新初始化属性
         */
        public void retrieveFromDB() {
            String query_sql = "select idValue from ids where idType = ?";
            // 首先判断对应的类型是否存在,不存在插入一个初始值0
            Integer result = (Integer) JDBCUtils.executeSQL(query_sql, idType);
            if (result.equals(-1)) {
                String insert_sql = "insert into ids(idValue,idType) values(?,?)";
                JDBCUtils.executeSQL(insert_sql, 0, idType);
            }
    
            // 先更新再取值
            String update_sql = "update ids set idValue = idValue + " + poolSize + " where idType = ?";
            JDBCUtils.executeSQL(update_sql, idType);
            result = (Integer) JDBCUtils.executeSQL(query_sql, idType);
    
            maxKey = result;
            minKey = result - poolSize + 1;
            nextKey = minKey;
        }
    
        public long getNextKey() {
            if (nextKey > maxKey) {
                retrieveFromDB();
            }
    
            return nextKey++;
        }
    
        public long getMaxKey() {
            return maxKey;
        }
    
        public void setMaxKey(long maxKey) {
            this.maxKey = maxKey;
        }
    
        public long getMinKey() {
            return minKey;
        }
    
        public void setMinKey(long minKey) {
            this.minKey = minKey;
        }
    
        public void setNextKey(long nextKey) {
            this.nextKey = nextKey;
        }
    
        public int getPoolSize() {
            return poolSize;
        }
    
        public void setPoolSize(int poolSize) {
            this.poolSize = poolSize;
        }
    
        public String getIdType() {
            return idType;
        }
    
        public void setIdType(String idType) {
            this.idType = idType;
        }
    
    } 

    KeyGenerator 内部维护一个集合,集合中保存的是KeyInfo对象的引用。

    package cn.qlq.singleton;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    public class KeyGenerator {
    
        private static KeyGenerator keyGenerator = new KeyGenerator();
    
        // 缓存相关
        private static final int POOL_SIZE = 20;
        private static ConcurrentHashMap<String, KeyInfo> KEYINFOS = new ConcurrentHashMap<>();
    
        private KeyGenerator() {
            // 防止反射创建实例
            if (keyGenerator != null) {
                throw new RuntimeException("not allowed!");
            }
        }
    
        /**
         * 静态工厂方法提供自己的实例
         * 
         * @return
         */
        public static KeyGenerator getInstance() {
            return keyGenerator;
        }
    
        /**
         * 获取下一个序列制
         * 
         * @param key
         *            序列的键
         * @return 自增后的值
         */
        public synchronized int getNextKey(String key) {
            KeyInfo keyInfo = null;
            if (KEYINFOS.containsKey(key)) {
                keyInfo = KEYINFOS.get(key);
            } else {
                keyInfo = new KeyInfo(POOL_SIZE, key);
                keyInfo.retrieveFromDB();
                KEYINFOS.put(key, keyInfo);
            }
    
            // 委托给KeyInfo
            return (int) keyInfo.getNextKey();
        }
    
    }

      从源码看出,每当getNextKey()被调用时,会先根据nextKey与maxKey值大小判断是否需要更新缓存区。如果系统重启并且缓存区的号码没有被用完,这些号码不会被再次使用。

    4. 多例模式的应用

      多例模式允许一个类有多个实例,这些实例各自有不同的内蕴状态。如下面的KeyGenerator就是以keyInfo作为其内蕴状态。内部的集合登记和保存自身的实例。

      客户端可以用静态工厂方法获取其所需要的KeyGenerator。这个静态工厂方法首先检查其集合里面是否有所需要的生成器,如果没有就创建一个并添加到集合中,在创建的同时创建keyInfo;如果有就直接返回。

    package cn.qlq.singleton;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    public class KeyGenerator {
    
        // 缓存相关
        private static final int POOL_SIZE = 20;
        private KeyInfo keyInfo;
    
        // 多例模式应用
        private static ConcurrentHashMap<String, KeyGenerator> keyGenerators = new ConcurrentHashMap<>();
    
        private KeyGenerator(String key) {
            keyInfo = new KeyInfo(POOL_SIZE, key);
            keyInfo.retrieveFromDB();
        }
    
        /**
         * 静态工厂方法提供自己的实例
         * 
         * @return
         */
        public static KeyGenerator getInstance(String keyName) {
            if (keyGenerators.containsKey(keyName)) {
                return keyGenerators.get(keyName);
            }
    
            KeyGenerator keyGenerator = new KeyGenerator(keyName);
            keyGenerators.put(keyName, keyGenerator);
            return keyGenerator;
        }
    
        /**
         * 获取下一个序列制
         * 
         * @param key
         *            序列的键
         * @return 自增后的值
         */
        public synchronized int getNextKey() {
            // 委托给KeyInfo
            return (int) keyInfo.getNextKey();
        }
    
        public KeyInfo getKeyInfo() {
            return keyInfo;
        }
    
        public void setKeyInfo(KeyInfo keyInfo) {
            this.keyInfo = keyInfo;
        }
    
        // 内部类
        private class KeyInfo {
    
            // 最大值
            private long maxKey;
    
            // 最小值
            private long minKey;
    
            // 下个值
            private long nextKey;
    
            // 池子大小
            private int poolSize;
    
            // 类型
            private String idType;
    
            public KeyInfo(int poolSize, String idType) {
                this.poolSize = poolSize;
                this.idType = idType;
            }
    
            /**
             * 从数据库取值并重新初始化属性
             */
            public void retrieveFromDB() {
                String query_sql = "select idValue from ids where idType = ?";
                // 首先判断对应的类型是否存在,不存在插入一个初始值0
                Integer result = (Integer) JDBCUtils.executeSQL(query_sql, idType);
                if (result.equals(-1)) {
                    String insert_sql = "insert into ids(idValue,idType) values(?,?)";
                    JDBCUtils.executeSQL(insert_sql, 0, idType);
                }
    
                // 先更新再取值
                String update_sql = "update ids set idValue = idValue + " + poolSize + " where idType = ?";
                JDBCUtils.executeSQL(update_sql, idType);
                result = (Integer) JDBCUtils.executeSQL(query_sql, idType);
    
                maxKey = result;
                minKey = result - poolSize + 1;
                nextKey = minKey;
            }
    
            public long getNextKey() {
                if (nextKey > maxKey) {
                    retrieveFromDB();
                }
    
                return nextKey++;
            }
    
            public long getMaxKey() {
                return maxKey;
            }
    
            public void setMaxKey(long maxKey) {
                this.maxKey = maxKey;
            }
    
            public long getMinKey() {
                return minKey;
            }
    
            public void setMinKey(long minKey) {
                this.minKey = minKey;
            }
    
            public void setNextKey(long nextKey) {
                this.nextKey = nextKey;
            }
    
            public int getPoolSize() {
                return poolSize;
            }
    
            public void setPoolSize(int poolSize) {
                this.poolSize = poolSize;
            }
    
            public String getIdType() {
                return idType;
            }
    
            public void setIdType(String idType) {
                this.idType = idType;
            }
        }
    
    }

      在这个设计里面KeyInfo类与上面设计一样,只是在这里作为内部类使用。

      下面是客户端代码,在调用工厂方法时需要传入序列建的名称作为参数,静态工厂根据键名反应对应的KeyGenerator实例;而在调用getNextKey()时无需传入参数。(其内部类KeyInfo自己维护了键名称)。

    package cn.qlq.singleton;
    
    import java.util.concurrent.CountDownLatch;
    
    public class MainClass {
    
        public static void main(String[] args) throws InterruptedException {
            // 制造一个闭锁
            final CountDownLatch countDownLatch = new CountDownLatch(2);
    
            // 模拟使用序列生成器
            for (int i = 0; i < 1; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 20; j++) {
                            String key = "测试序列号2";
                            System.out.println(key + "	" + KeyGenerator.getInstance(key).getNextKey());
                        }
    
                        countDownLatch.countDown();
                    }
                }).start();
            }
    
            // 阻塞
            countDownLatch.await();
        }
    
    }

    总结:

      在上面的方案中,多例模式和单例模式的缓存使用是具有实用价值的设计方案。

      在实际工作中用的是多例模式的方式,而且不带缓存,因为系统需要根据不同的key生成全局的编号,而且不能中断,所以没有采用缓存方案。个人倾向于单例模式+缓存的实现更加高效。

  • 相关阅读:
    CSS——清除浮动
    .net 多线程之线程取消
    .net 多线程临时变量
    NPOI helper
    添加学员存储过程
    SQL-sqlHelper001
    ado.net 中事务的使用
    T-SQL 事务2
    T-SQL 事务
    T-SQL 带参数存储过程
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/11011679.html
Copyright © 2011-2022 走看看