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 }