java 16位主键生成器_转载:主键生成器 Java

本文介绍了Java中主键生成器的实现,探讨了单例模式(饿汉式和懒汉式)以及多例模式在主键管理中的应用。通过KeyGenerator类和KeyInfo对象,展示了如何利用单例或多例模式来有效地生成和管理16位主键,同时考虑了多线程环境下的并发控制。
摘要由CSDN通过智能技术生成

1. 主键生成器

开发过数据库驱动信息系统的读者都知道,在一个关系数据库中,所有的数据都是存储在表里的,每一个表都有一个主键(Primary Key)。对大多数的用户输入数据来讲,主键需要由系统以序列号的方式产生,而不是由操作人员给出。

某些关系数据库引擎提供某种序列键生成机制。如SQL Server允许每一个表内可以有一个自动编号列。Oracle提供Sequence对象,可以提供序列键值。

但某些数据库引擎则没有相应的机制,如Sybase。这时就需要我们自己去生成主键序列号。通常的做法是使用一个表来存储所有的主键最大值。这个表包含两个列,一个列存放键名,另一个列存放键值,客户端使用SQL语句自行管理键值。

由于系统运行期间总是需要序列键,因此整个系统需要一个序列键管理对象,这个对象在运行期间存在。考虑到可以让一个序列键管理器负责管理分属于不同模块的多个序列键,因此这个序列键管理器需要让整个系统访问。

2. 单例模式

单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

2.1 饿汉式单例

饿汉式单例是在Java里实现最为简单的单例类,其源代码如下所示:

public classEagerSingleton {privateEagerSingleton() {

}//静态工厂方法

public staticEagerSingleton getInstance() {returnm_instance;

}private static finalEagerSingleton m_instance= newEagerSingleton();

}

在这个类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用。这时单例类的唯一实例就被创建出来了。

Java语言中单例类最重要的特点就是类的构造函数是私有的,从而避免外界利用构造函数直接创建出任意多的实例。由于构造函数是私有的,因此此类不能被继承。

2.2 懒汉式单例

与饿汉式单例相同之处是,类的构造函数是私有的,不同的是懒汉式单例在第一次被引用时将自己实例化。如果加载器是静态的,那么懒汉式单例类被加载时不会将自己实例化。

public classLazySingleton {privateLazySingleton() {

}synchronized public staticLazySingleton getInstance() {if (m_instance == null) {

m_instance= newLazySingleton();

}returnm_instance;

}private static LazySingleton m_instance = null;

}

可以看到,在上面给出的懒汉式单例类实现里对静态工厂方法使用了同步化,以处理多线程环境。同样由于构造函数是私有的,因此此类不能被继承。

饿汉式单例类在自己被加载时就将自己初始化,从资源利用效率角度来讲,比懒汉式单例类稍差一些。然而懒汉式单例类在将自己实例化时,必须处理好在多个线程同时首次引用此类时的访问限制问题。

2.3 单例类的状态

一个单例类是可以有状态的,一个有状态的单例对象一般也是可变的单例对象。有状态的单例对象常常当做状态库(repositary)使用。比如一个单例对象可以持有一个int类型的属性,用来给系统提供一个数值唯一的序列号码。

另一方面,单例类也可以没有状态,仅用作提供工具性函数的对象。一个没有状态的单例类也就是一个不变单例类。

3. 多例模式

单例模式很容易推广到任意且有有限多个实例的情况,这时候称它为多例模式。多例模式除了一个类可以有多个实例之外,特点与单例模式类似。

多例模式的实例数目并不需要有上限,由于没有上限的多例类对实例的数目是没有限制的,因此虽然这种多例类是单例模式的推广,但是这种多例类并不一定能够回到单例类。

由于事先不知道要创建多少个实例,因此,必然使用聚集管理所有的实例。

4. 单例模式的应用

我们回到那个主键生成器。读者应该已经意识到这个系统设计应当使用到单例模式。

这个设计由一个单例类KeyGenerator和一个存储某一个键的信息的KeyInfo对象组成。源代码如下:

public classKeyGenerator {private staticKeyGenerator keygen= newKeyGenerator();private static final int POOL_SIZE = 20;private HashMap keyList = new HashMap(10);privateKeyGenerator() {

}public staticKeyGenerator getInstance() {returnkeygen;

}public intgetNextKey(String keyName) {

KeyInfo keyinfo;if(keyList.containsKey(keyName)) {

keyinfo=(KeyInfo) keyList.get(keyName);

System.out.println("key found");

}else{

keyinfo= newKeyInfo(POOL_SIZE, keyName);

keyList.put(keyName, keyinfo);

System.out.println("new key created");

}returnkeyinfo.getNextKey();

}

}

可以看出KeyGenerator是一个单例类,它提供了私有构造函数和一个静态工厂方法向外界提供自己唯一的实例。

一个系统中往往不止一个主键,我们使用一个聚集keyList来存储不同序列键信息的KeyInfo对象。

下面是KeyInfo类的:

public classKeyInfo{

private intkeyMax;private intkeyMin;private intnextKey;private intpoolSize;privateString keyName;public KeyInfo(intpoolSize, String keyName) {this.poolSize =poolSize;this.keyName =keyName;

retrieveFromDB();

}public intgetKeyMax() {returnkeyMax;

}public intgetKeyMin() {returnkeyMin;

}public synchronized intgetNextKey() {if (nextKey >keyMax) {

retrieveFromDB();

}return nextKey++;

}private voidretrieveFromDB() {//step1:从数据库获取poolSize个新KeyValueString sql1= "UPDATE KeyTable SET keyValue = keyValue + " +poolSize+ " WHERE keyName = '" + keyName+ "'"; //step2:获取最新的KeyValue值

String sql:2 = "SELECT keyValue FROM KeyTable WHERE KeyName = '" + keyName + "'";

//在一个事务中执行上面的操作并提交//假设返回的keyValue值为1000

intkeyFromDB = 1000;

keyMax=keyFromDB;

keyMin= keyFromDB - poolSize + 1;

nextKey=keyMin;

}

}

使用KeyInfo的目的是不用每次都进行键值的查询。毕竟一个键只是一些序列号码,与其每接到一次请求就查询一次不如一次性的预先登记多个键值,然后连续多次地向客户端提供这些预定的键值。这就是键值的缓存机制。当KeyGenerator每次更新数据库中数据时,它都将键值增加,但不是加1而是更多,我们这个例子中增加的值是20。为了存储所有的与键有关的信息,使用KeyInfo。

这个KeyInfo除了存储与键有关的信息外,还提供一个retrieveFromDB()方法,向数据库查询键值。每次查询得到的20个键值会在随后提供给请求者,直到20个键值全部使用完毕,然后再向数据库预定后20个键值。KeyGenerator保持一个对KeyInfo对象的引用。客户端调用getNextKey()方法以得到下一个键的键值。

下面是一个示意性的客户端Client类的源代码:

public classClient {private staticKeyGenerator keygen;public static voidmain(String[] args) {

keygen=KeyGenerator.getInstance();for (int i = 0; i < 25; i++) {

System.out.println("key(" + (i + 1) + ")= " + keygen.getNextKey("PO_NUMBER"));

}

}

}

5.多例模式的应用

正如前面所谈到的,为了能够处理多系列键值的情况,除了可以将单例模式所封装的单一状态改为聚集状态之外,还可以采用多例模式,下面是KeyGenerator的源代码。可以看出,这是一个多例类,每一个KeyGenerator对象都持有一个特定的KeyInfo对象作为内蕴状态。客户端可以使用这个类的静态工厂方法得到所需的实例,而这个工厂方法会首先查看作登记用的keygens聚集。如果所要求的键名在聚集里面,就直接将这个键名所对应的实例返还给客户端,如果不存在就创建一个新的实例。

importjava.util.HashMap;public classKeyGenerator {private static HashMap kengens = new HashMap(10);private static final int POOL_SIZE = 20;privateKeyInfo keyinfo;privateKeyGenerator() {

}privateKeyGenerator(String keyName) {

keyinfo= newKeyInfo(POOL_SIZE, keyName);

}public static synchronizedKeyGenerator getInstance(String keyName) {

KeyGenerator keygen;if(kengens.containsKey(keyName)) {

keygen=(KeyGenerator) kengens.get(keyName);

}else{

keygen= newKeyGenerator(keyName);

}returnkeygen;

}public intgetNextKey() {returnkeyinfo.getNextKey();

}

}

KeyInfo与单例类中一样,下面是客户端代码:

public classClient {private staticKeyGenerator keygen;public static voidmain(String[] args) {

keygen= KeyGenerator.getInstance("PO_NUMBER");for (int i = 0; i < 25; i++) {

System.out.println("key(" + (i + 1) + ")= " +keygen.getNextKey());

}

}

}

高并发分布式系统中生成全局唯一Id汇总 数据在分片时,典型的是分库分表,就有一个全局ID生成的问题。 单纯的生成全局ID并不是什么难题,但是生成的ID通常要满足分片的一些要求: 1 不能有单点故障。 2 以时间为序,或者ID里包含时间。这样一是可以少一个索引,二是冷热数据容易分离。 3 可以控制ShardingId。比如某一个用户的文章要放在同一个分片内,这样查询效率高,修改也容易。 4 不要太长,最好64bit。使用long比较好操作,如果是96bit,那就要各种移相当的不方便,还有可能有些组件不能支持这么大的ID。 一 twitter twitter在把存储系统从MySQL迁移到Cassandra的过程中由于Cassandra没有顺序ID生成机制,于是自己开发了一套全局唯一ID生成服务:Snowflake。 1 41的时间序列(精确到毫秒,41的长度可以使用69年) 2 10的机器标识(10的长度最多支持部署1024个节点) 3 12的计数顺序号(12的计数顺序号支持每个节点每毫秒产生4096个ID序号) 最高是符号,始终为0。 优点:高性能,低延迟;独立的应用;按时间有序。 缺点:需要独立的开发和部署。 原理 java 实现代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class IdWorker { private final long workerId; private final static long twepoch = 1288834974657L; private long sequence = 0L; private final static long workerIdBits = 4L; public final static long maxWorkerId = -1L ^ -1L << workerIdBits; private final static long sequenceBits = 10L; private final static long workerIdShift = sequenceBits; private final static long timestampLeftShift = sequenceBits + workerIdBits; public final static long sequenceMask = -1L ^ -1L < this.maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format( "worker Id can't be greater than %d or less than 0", this.maxWorkerId)); } this.workerId = workerId; } public synchronized long nextId() { long timestamp = this.timeGen(); if (this.lastTimestamp == timestamp) { this.sequence = (this.sequence + 1) & this.sequenceMask; if (this.sequence == 0) { System.out.println("###########" + sequenceMask); timestamp = this.tilNextMillis(this.lastTimestamp); } } else { this.sequence = 0; } if (timestamp < this.lastTimestamp) { try { throw new Exception( String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds", this.lastTimestamp - timestamp)); } catch (Exception e) { e.printStackTrace(); } } this.lastTimestamp = timestamp; long nextId = ((timestamp - twepoch << timestampLeftShift)) | (this.workerId << this.workerIdShift) | (this.sequence); System.out.println("timestamp:" + timestamp + ",timestampLeftShift:" + timestampLeftShift + ",nextId:" + nextId + ",workerId:" + workerId + ",sequence:" + sequence); return nextId; } private long tilNextMillis(final long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return timestamp; } private long timeGen() { return System.currentTimeMillis(); } public static void main(String[] args){ IdWorker worker2 = new IdWorker(2); System.out.println(worker2.nextId()); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值