简易序列号实现
1 package com.itch; 2 import java.text.SimpleDateFormat; 3 import java.util.Calendar; 4 import java.util.Date; 5 import java.util.concurrent.atomic.AtomicInteger; 6 import java.util.concurrent.atomic.AtomicReference; 7 8 /** 9 * <p> 本类可以简易生成唯一序列号,线程安全<b>【待验证】</b> 10 * <p> 序列号可由yyyyMMddHHmmss + [machineId(机器id) + tid(运行线程id)] + index(序号) 等部分组成 11 * <p> 实现思路:以当前运行时间,每秒钟先按序分配(从下标0开始)。若当前秒不足以分配,则向未来秒数进行预分配 12 * <hr> 13 * <em> 仅供学习参考,欢迎大家一起测试,争取能用到生产环境。lol.. 14 * @author tianchong_01011@163.com 15 * @version 0.1 16 */ 17 public final class SimpleUniqueNo { 18 19 /** 20 * 默认每秒可分配序号位数 21 */ 22 public static final int DEFAULT_INDEX_BIT_PER_SECOND = 4; 23 24 /** 25 * 默认machine id 26 */ 27 public static final String DEFAULT_MACHINE_ID = "00"; 28 29 /** 30 * 计数器 31 */ 32 private volatile AtomicInteger indexCounter; 33 34 /** 35 * 上一次执行时间 36 */ 37 private volatile Date lastestTime; 38 39 /** 40 * 超过每秒最大分配时的基准时间 41 */ 42 private AtomicReference<Date> overBaseTime; 43 44 /** 45 * 日期格式化对象 46 */ 47 private ThreadLocal<SimpleDateFormat> threadLocal; 48 49 /** 50 * 预分配最大秒数,主要是为了空闲时重置 {@link #indexCounter},防止溢出 51 */ 52 private volatile int maxOffset; 53 54 /** 55 * int 溢出次数 56 */ 57 private volatile int intOverflowTimes; 58 59 /** 60 * 每秒可分配序号位数 61 */ 62 private final int indexBitPerSecond; 63 64 private final int maxIndexNum; 65 66 /** 67 * 每秒请求数 68 */ 69 private volatile AtomicInteger threadNumPerSecond; 70 71 /** 72 * 机器编号 73 */ 74 private final String machineId; 75 76 public SimpleUniqueNo() { 77 this(DEFAULT_INDEX_BIT_PER_SECOND); 78 } 79 80 /** 81 * 82 * @param indexBitPerSecond 每秒分配位数,由调用者保证位数不会造成interger溢出 83 */ 84 public SimpleUniqueNo(int indexBitPerSecond) { 85 this(indexBitPerSecond, DEFAULT_MACHINE_ID); 86 } 87 88 /** 89 * 90 * @param indexBitPerSecond 每秒分配位数,由调用者保证位数不会造成interger溢出 91 * @param machineNo 机器号 92 */ 93 public SimpleUniqueNo(int indexBitPerSecond, String machineId) { 94 super(); 95 this.indexCounter = new AtomicInteger(); 96 this.threadLocal = new ThreadLocal<>(); 97 this.lastestTime = new Date(); 98 this.overBaseTime = new AtomicReference<>(); 99 this.indexBitPerSecond = indexBitPerSecond; 100 this.machineId = machineId; 101 StringBuilder builder = new StringBuilder("1"); 102 for (int i=0; i<indexBitPerSecond; i++) { 103 builder.append("0"); 104 } 105 this.maxIndexNum = Integer.parseInt(builder.toString()); 106 this.threadNumPerSecond = new AtomicInteger(); 107 } 108 109 public String genNo() { 110 SimpleDateFormat sdf = threadLocal.get(); 111 if (null == sdf) { 112 sdf = new SimpleDateFormat("yyyyMMddHHmmss"); 113 threadLocal.set(sdf); 114 } 115 116 Date date = new Date(); 117 Calendar cd = Calendar.getInstance(); 118 cd.setTime(date); 119 String ns = "" + cd.get(Calendar.YEAR) + cd.get(Calendar.MONTH) + cd.get(Calendar.DAY_OF_MONTH) 120 + cd.get(Calendar.HOUR_OF_DAY) + cd.get(Calendar.MINUTE) + cd.get(Calendar.SECOND); 121 cd.setTime(lastestTime); 122 String os = "" + cd.get(Calendar.YEAR) + cd.get(Calendar.MONTH) + cd.get(Calendar.DAY_OF_MONTH) 123 + cd.get(Calendar.HOUR_OF_DAY) + cd.get(Calendar.MINUTE) + cd.get(Calendar.SECOND); 124 125 int i = indexCounter.get(); 126 if (i < maxIndexNum && ns.hashCode() != os.hashCode() && lastestTime.before(date)) { // 新的1s 重置计数 127 synchronized (this) { 128 cd.setTime(lastestTime); 129 os = "" + cd.get(Calendar.YEAR) + cd.get(Calendar.MONTH) + cd.get(Calendar.DAY_OF_MONTH) 130 + cd.get(Calendar.HOUR_OF_DAY) + cd.get(Calendar.MINUTE) + cd.get(Calendar.SECOND); 131 i = indexCounter.get(); 132 if (i < maxIndexNum && ns.hashCode() != os.hashCode() && lastestTime.before(date)) { // 防止更新过期时间 133 Date oldLastestTime = lastestTime; 134 while (threadNumPerSecond.get() > 0) { 135 Thread.yield(); 136 } 137 i = indexCounter.get(); 138 if ((intOverflowTimes == 0 && i < maxIndexNum)) { // 非溢出借位 重置 139 indexCounter.set(0); 140 } else { // idle reset avoid integer overflow 141 long offset = date.getTime() - oldLastestTime.getTime(); 142 if (offset / 1000 > maxOffset) { 143 indexCounter.set(0); 144 intOverflowTimes = 0; 145 maxOffset = 0; 146 overBaseTime.set(null); 147 } 148 } 149 150 lastestTime = date; 151 } 152 } 153 } 154 155 threadNumPerSecond.getAndIncrement(); 156 int c = indexCounter.getAndIncrement(); 157 threadNumPerSecond.getAndDecrement(); 158 if (c < 0) { // integer overflow 159 c = indexCounter.get(); 160 if (indexCounter.compareAndSet(c, 0)) { 161 intOverflowTimes++; 162 } else { 163 while (indexCounter.get() < 0) { // waitting 164 // do nothing 165 } 166 } 167 168 c = indexCounter.getAndIncrement(); 169 } 170 171 if (intOverflowTimes > 0 || c >= maxIndexNum) { // 溢出借未来时间 172 date = overBaseTime.get() != null ? overBaseTime.get() : date; 173 cd.setTime(date); 174 overBaseTime.compareAndSet(null, date); 175 int offset = (c / maxIndexNum) + (Integer.MAX_VALUE / maxIndexNum) * intOverflowTimes; 176 cd.add(Calendar.SECOND, offset); 177 date = cd.getTime(); 178 179 maxOffset = Math.max(maxOffset, offset); 180 c = c % maxIndexNum; 181 } 182 183 String time = null, mid = null, tid = null, index = null; 184 time = sdf.format(date); 185 mid = machineId; 186 // tid = "" + Thread.currentThread().getId(); 187 index = String.format("%0" + indexBitPerSecond + "d", c); 188 return time + mid + index; 189 } 190 191 public static void main(String[] args) { 192 // 单机A系统 193 SimpleUniqueNo unique = new SimpleUniqueNo(); 194 System.out.println(unique.genNo()); 195 // 单机其他系统 196 // SimpleUniqueNo unique = new SimpleUniqueNo(); 197 // System.out.println(unique.genNo()); 198 199 // 多机 200 SimpleUniqueNo u1 = new SimpleUniqueNo(DEFAULT_INDEX_BIT_PER_SECOND, "01"); 201 SimpleUniqueNo u2 = new SimpleUniqueNo(DEFAULT_INDEX_BIT_PER_SECOND, "02"); 202 System.out.println(u1.genNo()); 203 System.out.println(u2.genNo()); 204 } 205 206 }