1.snowflake簡介
網際網路快速發展的今天,分布式應用系統已經見怪不怪,在分布式系統中,我們需要各種各樣的ID,既然是ID那麼必然是要保證全局唯一,除此之外,不同當業務還需要不同的特性,比如像并發巨大的業務要求ID生成效率高,吞吐大;比如某些銀行類業務,需要按每日日期制定交易流水号;又比如我們希望使用者的ID是随機的,無序的,純數字的,且位數長度是小于10位的。等等,不同的業務場景需要的ID特性各不一樣,于是,衍生了各種ID生成器,但大多數利用資料庫控制ID的生成,性能受資料庫并發能力限制,那麼有沒有一款不需要依賴任何中間件(如資料庫,分布式緩存服務等)的ID生成器呢?本着取之于開源,用之于開源的原則,今天,特此介紹Twitter開源的一款分布式自增ID算法snowflake,并附上算法原理推導和演算過程!
snowflake算法是一款本地生成的(ID生成過程不依賴任何中間件,無網絡通信),保證ID全局唯一,并且ID總體有序遞增,性能每秒生成300w+。

- 1bit:一般是符号位,不做處理
- 41bit:用來記錄時間戳,這裡可以記錄69年,如果設定好起始時間比如今年是2018年,那麼可以用到2089年,到時候怎麼辦?要是這個系統能用69年,我相信這個系統早都重構了好多次了。
- 10bit:10bit用來記錄機器ID,總共可以記錄1024台機器,一般用前5位代表資料中心ID,後面5位是某個資料中心的機器ID
- 12bit:循環位,用來對同一個毫秒之内産生不同的ID,12位可以最多記錄4095個,也就是在同一個機器同一毫秒最多記錄4095個,多餘的需要進行等待下毫秒。
<p>總體來說,在工作節點達到1024頂配的場景下,SnowFlake算法在同一毫秒内最多可以生成多少個全局唯一ID呢?這是一個簡單的乘法:</p> <p>同一毫秒的ID數量 = 1024 X 4096 = 4194304</p> <p>400多萬個ID,這個數字在絕大多數并發場景下都是夠用的。</p> <p>snowflake 算法中,第三個部分是工作機器ID,可以結合上一節的命名方法,并通過Zookeeper管理workId,免去手動頻繁修改叢集節點,去配置機器ID的麻煩。</p> </li>
上面隻是一個将64bit劃分的标準,當然也不一定這麼做,可以根據不同業務的具體場景來劃分,比如下面給出一個業務場景:
- 服務目前QPS10萬,預計幾年之内會發展到百萬。
- 目前機器三地部署,上海,北京,深圳都有。
- 目前機器10台左右,預計未來會增加至百台。
這個時候我們根據上面的場景可以再次合理的劃分62bit,QPS幾年之内會發展到百萬,那麼每毫秒就是千級的請求,目前10台機器那麼每台機器承擔百級的請求,為了保證擴充,後面的循環位可以限制到1024,也就是2^10,那麼循環位10位就足夠了。
機器三地部署我們可以用3bit總共8來表示機房位置,目前的機器10台,為了保證擴充到百台那麼可以用7bit 128來表示,時間位依然是41bit,那麼還剩下64-10-3-7-41-1 = 2bit,還剩下2bit可以用來進行擴充。
适用場景:當我們需要無序不能被猜測的ID,并且需要一定高性能,且需要long型,那麼就可以使用我們雪花算法。比如常見的訂單ID,用雪花算法别人就發猜測你每天的訂單量是多少。
上面定義了雪花算法的實作,在nextId中是我們生成雪花算法的關鍵。
2.防止時鐘回撥
因為機器的原因會發生時間回撥,我們的雪花算法是強依賴我們的時間的,如果時間發生回撥,有可能會生成重複的ID,在我們上面的nextId中我們用目前時間和上一次的時間進行判斷,如果目前時間小于上一次的時間那麼肯定是發生了回撥,普通的算法會直接抛出異常,這裡我們可以對其進行優化,一般分為兩個情況:
- 如果時間回撥時間較短,比如配置5ms以内,那麼可以直接等待一定的時間,讓機器的時間追上來。
- 如果時間的回撥時間較長,我們不能接受這麼長的阻塞等待,那麼又有兩個政策:
- 直接拒絕,抛出異常,打日志,通知RD時鐘復原。
- 利用擴充位,上面我們讨論過不同業務場景位數可能用不到那麼多,那麼我們可以把擴充位數利用起來了,比如當這個時間回撥比較長的時候,我們可以不需要等待,直接在擴充位加1。2位的擴充位允許我們有3次大的時鐘回撥,一般來說就夠了,如果其超過三次我們還是選擇抛出異常,打日志。
通過上面的幾種政策可以比較的防護我們的時鐘回撥,防止出現回撥之後大量的異常出現。下面是修改之後的代碼,這裡修改了時鐘回撥的邏輯:
最終代碼如下:
- import org.apache.commons.lang3.RandomUtils;
- import org.apache.commons.lang3.StringUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import java.lang.management.ManagementFactory;
- import java.net.InetAddress;
- import java.net.NetworkInterface;
- import java.net.UnknownHostException;
- import java.text.SimpleDateFormat;
- import java.util.*;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.LockSupport;
- /**
- * 分布式全局ID雪花算法解決方案
- *
- * 防止時鐘回撥
- * 因為機器的原因會發生時間回撥,我們的雪花算法是強依賴我們的時間的,如果時間發生回撥,
- * 有可能會生成重複的ID,在我們上面的nextId中我們用目前時間和上一次的時間進行判斷,
- * 如果目前時間小于上一次的時間那麼肯定是發生了回撥,
- * 普通的算法會直接抛出異常,這裡我們可以對其進行優化,一般分為兩個情況:
- * 如果時間回撥時間較短,比如配置5ms以内,那麼可以直接等待一定的時間,讓機器的時間追上來。
- * 如果時間的回撥時間較長,我們不能接受這麼長的阻塞等待,那麼又有兩個政策:
- * 直接拒絕,抛出異常,打日志,通知RD時鐘復原。
- * 利用擴充位,上面我們讨論過不同業務場景位數可能用不到那麼多,那麼我們可以把擴充位數利用起來了,
- * 比如當這個時間回撥比較長的時候,我們可以不需要等待,直接在擴充位加1。
- * 2位的擴充位允許我們有3次大的時鐘回撥,一般來說就夠了,如果其超過三次我們還是選擇抛出異常,打日志。
- * 通過上面的幾種政策可以比較的防護我們的時鐘回撥,防止出現回撥之後大量的異常出現。下面是修改之後的代碼,這裡修改了時鐘回撥的邏輯:
- */
- public class SnowflakeIdFactory {
- private static final Logger log = LoggerFactory.getLogger(SnowflakeIdFactory.class);
- /**
- * EPOCH是伺服器第一次上線時間點, 設定後不允許修改
- * 2018/9/29日,從此時開始計算,可以用到2089年
- */
- private static long EPOCH = 1538211907857L;
- /**
- * 每台workerId伺服器有3個備份workerId, 備份workerId數量越多, 可靠性越高, 但是可部署的sequence ID服務越少
- */
- private static final long BACKUP_COUNT = 3;
- /**
- * worker id 的bit數,最多支援8192個節點
- */
- private static final long workerIdBits = 5L;
- /**
- * 資料中心辨別位數
- */
- private static final long dataCenterIdBits = 5L;
- /**
- * 序列号,支援單節點最高每毫秒的最大ID數4096
- * 毫秒内自增位
- */
- private static final long sequenceBits = 12L;
- /**
- * 機器ID偏左移12位
- */
- private static final long workerIdShift = sequenceBits;
- /**
- * 資料中心ID左移17位(12+5)
- */
- private static final long dataCenterIdShift = sequenceBits + workerIdBits;
- /**
- * 時間毫秒左移22位(5+5+12)
- */
- private static final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
- /**
- * sequence掩碼,確定sequnce不會超出上限
- * 最大的序列号,4096
- * -1 的補碼(二進制全1)右移12位, 然後取反
- * 生成序列的掩碼,這裡為4095 (0b111111111111=0xfff=4095)
- */
- private static final long sequenceMask = -1L ^ (-1L << sequenceBits);
- //private final static long sequenceMask = ~(-1L << sequenceBits);
- /**
- * 實際的最大workerId的值 結果是31,8091
- * workerId原則上上限為1024, 但是需要為每台sequence服務預留BACKUP_AMOUNT個workerId,
- * (這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數)
- */
- //private static final long maxWorkerId = (1L << workerIdBits) / (BACKUP_COUNT + 1);
- //原來代碼 -1 的補碼(二進制全1)右移13位, 然後取反
- private static final long maxWorkerId = -1L ^ (-1L << workerIdBits);
- //private final static long maxWorkerId = ~(-1L << workerIdBits);
- /**
- * 支援的最大資料辨別id,結果是31
- */
- private static final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
- /**
- * long workerIdBits = 5L;
- * -1L 的二進制: 1111111111111111111111111111111111111111111111111111111111111111
- * -1L<<workerIdBits = -32 ,二進制: 1111111111111111111111111111111111111111111111111111111111100000
- * workerMask= -1L ^ -32 = 31, 二進制: 11111
- */
- private static long workerMask= -1L ^ (-1L << workerIdBits);
- //程序編碼
- private long processId = 1L;
- private static long processMask=-1L ^ (-1L << dataCenterIdBits);
- /**
- * 工作機器ID(0~31)
- * snowflake算法給workerId預留了10位,即workId的取值範圍為[0, 1023],
- * 事實上實際生産環境不大可能需要部署1024個分布式ID服務,
- * 是以:将workerId取值範圍縮小為[0, 511],[512, 1023]
- * 這個範圍的workerId當做備用workerId。workId為0的備用workerId是512,
- * workId為1的備用workerId是513,以此類推
- */
- private static long workerId;
- /**
- * 資料中心ID(0~31)
- */
- private long dataCenterId;
- /**
- * 目前毫秒生成的序列
- */
- private long sequence = 0L;
- /**
- * 上次生成ID的時間戳
- */
- private long lastTimestamp = -1L;
- private long extension = 0L;
- private long maxExtension = 0L;
- /**
- * 保留workerId和lastTimestamp, 以及備用workerId和其對應的lastTimestamp
- */
- private static Map<Long, Long> workerIdLastTimeMap = new ConcurrentHashMap<>();
- /**
- * 最大容忍時間, 機關毫秒, 即如果時鐘隻是回撥了該變量指定的時間, 那麼等待相應的時間即可;
- * 考慮到sequence服務的高性能, 這個值不易過大
- */
- private static final long MAX_BACKWARD_MS = 3;
- private static SnowflakeIdFactory idWorker;
- static {
- idWorker = new SnowflakeIdFactory();
- }
- static {
- Calendar calendar = Calendar.getInstance();
- calendar.set(2018, Calendar.NOVEMBER, 1);
- calendar.set(Calendar.HOUR_OF_DAY, 0);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
- calendar.set(Calendar.MILLISECOND, 0);
- // EPOCH是伺服器第一次上線時間點, 設定後不允許修改
- EPOCH = calendar.getTimeInMillis();
- // 初始化workerId和其所有備份workerId與lastTimestamp
- // 假設workerId為0且BACKUP_AMOUNT為4, 那麼map的值為: {0:0L, 256:0L, 512:0L, 768:0L}
- // 假設workerId為2且BACKUP_AMOUNT為4, 那麼map的值為: {2:0L, 258:0L, 514:0L, 770:0L}
- /* for (int i = 0; i<= BACKUP_COUNT; i++){
- workerIdLastTimeMap.put(workerId + (i * maxWorkerId), 0L);
- }*/
- }
- //成員類,IdGenUtils的執行個體對象的儲存域
- private static class SnowflakeIdGenHolder {
- private static final SnowflakeIdFactory instance = new SnowflakeIdFactory();
- }
- //外部調用擷取IdGenUtils的執行個體對象,確定不可變
- public static SnowflakeIdFactory getInstance(){
- return SnowflakeIdGenHolder.instance;
- }
- /**
- * 靜态工具類
- *
- * @return
- */
- public static Long generateId(){
- long id = idWorker.nextId();
- return id;
- }
- //初始化構造,無參構造有參函數,預設節點都是0
- public SnowflakeIdFactory(){
- //this(0L, 0L);
- this.dataCenterId = getDataCenterId(maxDataCenterId);
- //擷取機器編碼
- this.workerId = getWorkerId(dataCenterId, maxWorkerId);
- }
- /**
- * 構造函數
- * @param workerId 工作ID (0~31)
- * @param dataCenterId 資料中心ID (0~31)
- */
- public SnowflakeIdFactory(long workerId, long dataCenterId) {
- if (workerId > maxWorkerId || workerId < 0) {
- throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
- }
- if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
- throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDataCenterId));
- }
- this.workerId = workerId;
- this.dataCenterId = dataCenterId;
- }
- /**
- * 擷取帶自定義字首的全局唯一編碼
- */
- public String getStrCodingByPrefix(String prefix){
- Long ele = this.nextId();
- return prefix + ele.toString();
- }
- /**
- * 獲得下一個ID (該方法是線程安全的)
- * 在單節點上獲得下一個ID,使用Synchronized控制并發,而非CAS的方式,
- * 是因為CAS不适合并發量非常高的場景。
- *
- * 考慮時鐘回撥
- * 缺陷: 如果連續兩次時鐘回撥, 可能還是會有問題, 但是這種機率極低極低
- * @return
- */
- public synchronized long nextId() {
- long currentTimestamp = timeGen();
- // 當發生時鐘回撥時
- if (currentTimestamp < lastTimestamp){
- // 如果時鐘回撥在可接受範圍内, 等待即可
- long offset = lastTimestamp - currentTimestamp;
- if ( offset <= MAX_BACKWARD_MS){
- try {
- //睡(lastTimestamp - currentTimestamp)ms讓其追上
- LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(offset));
- //時間偏差大小小于5ms,則等待兩倍時間
- //wait(offset << 1);
- //Thread.sleep(waitTimestamp);
- currentTimestamp = timeGen();
- //如果時間還小于目前時間,那麼利用擴充字段加1
- //或者是采用抛異常并上報
- if (currentTimestamp < lastTimestamp) {
- //擴充字段
- //extension += 1;
- //if (extension > maxExtension) {
- //伺服器時鐘被調整了,ID生成器停止服務.
- throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - currentTimestamp));
- //}
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }else {
- //擴充字段
- /*extension += 1;
- if (extension > maxExtension) {
- //伺服器時鐘被調整了,ID生成器停止服務.
- throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - currentTimestamp));
- }*/
- tryGenerateKeyOnBackup(currentTimestamp);
- }
- }
- //對時鐘回撥簡單處理
- /* if (currentTimestamp < lastTimestamp) {
- //伺服器時鐘被調整了,ID生成器停止服務.
- throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - currentTimestamp));
- }*/
- // 如果和最後一次請求處于同一毫秒, 那麼sequence+1
- if (lastTimestamp == currentTimestamp) {
- // 如果目前生成id的時間還是上次的時間,那麼對sequence序列号進行+1
- sequence = (sequence + 1) & sequenceMask;
- if (sequence == 0) {
- //自旋等待到下一毫秒
- currentTimestamp = waitUntilNextTime(lastTimestamp);
- }
- //判斷是否溢出,也就是每毫秒内超過4095,當為4096時,與sequenceMask相與,sequence就等于0
- /*if (sequence == sequenceMask) {
- // 目前毫秒生成的序列數已經大于最大值,那麼阻塞到下一個毫秒再擷取新的時間戳
- currentTimestamp = this.waitUntilNextTime(lastTimestamp);
- }*/
- } else {
- // 如果是一個更近的時間戳, 那麼sequence歸零
- sequence = 0L;
- }
- // 更新上次生成id的時間戳
- lastTimestamp = currentTimestamp;
- // 更新map中儲存的workerId對應的lastTimestamp
- //workerIdLastTimeMap.put(this.workerId, lastTimestamp);
- if (log.isDebugEnabled()) {
- log.debug("{}-{}-{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(lastTimestamp)), workerId, sequence);
- }
- // 進行移位操作生成int64的唯一ID
- //時間戳右移動23位
- long timestamp = (currentTimestamp - EPOCH) << timestampLeftShift;
- //workerId 右移動10位
- long workerId = this.workerId << workerIdShift;
- //dataCenterId 右移動(sequenceBits + workerIdBits = 17位)
- long dataCenterId = this.dataCenterId << dataCenterIdShift;
- return timestamp | dataCenterId | workerId | sequence;
- }
- /**
- * 嘗試在workerId的備份workerId上生成
- * 核心優化代碼在方法tryGenerateKeyOnBackup()中,BACKUP_COUNT即備份workerId數越多,
- * sequence服務避免時鐘回撥影響的能力越強,但是可部署的sequence服務越少,
- * 設定BACKUP_COUNT為3,最多可以部署1024/(3+1)即256個sequence服務,完全夠用,
- * 抗時鐘回撥影響的能力也得到非常大的保障。
- * @param currentMillis 目前時間
- */
- private long tryGenerateKeyOnBackup(long currentMillis){
- // 周遊所有workerId(包括備用workerId, 檢視哪些workerId可用)
- for (Map.Entry<Long, Long> entry:workerIdLastTimeMap.entrySet()){
- this.workerId = entry.getKey();
- // 取得備用workerId的lastTime
- Long tempLastTime = entry.getValue();
- lastTimestamp = tempLastTime==null?0L:tempLastTime;
- // 如果找到了合适的workerId
- if (lastTimestamp<=currentMillis){
- return lastTimestamp;
- }
- }
- // 如果所有workerId以及備用workerId都處于時鐘回撥, 那麼抛出異常
- throw new IllegalStateException("Clock is moving backwards, current time is "
- +currentMillis+" milliseconds, workerId map = " + workerIdLastTimeMap);
- }
- /**
- * 阻塞到下一個毫秒,直到獲得新的時間戳
- * @param lastTimestamp 上次生成ID的時間截
- * @return 目前時間戳
- */
- protected long waitUntilNextTime(long lastTimestamp) {
- long timestamp = timeGen();
- while (timestamp <= lastTimestamp) {
- timestamp = timeGen();
- }
- return timestamp;
- }
- protected long timeGen() {
- return System.currentTimeMillis();
- }
- /**
- * 擷取WorkerId
- * @param dataCenterId
- * @param maxWorkerId
- * @return
- */
- protected static long getWorkerId(long dataCenterId, long maxWorkerId) {
- StringBuffer mpid = new StringBuffer();
- mpid.append(dataCenterId);
- String name = ManagementFactory.getRuntimeMXBean().getName();
- if (!name.isEmpty()) {
- // GET jvmPid
- mpid.append(name.split("@")[0]);
- }
- // MAC + PID 的 hashcode 擷取16個低位
- return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
- }
- /**
- * 擷取機器編碼 用來做資料ID
- * 資料辨別id部分 通常不建議采用下面的MAC位址方式,
- * 因為使用者通過破解很容易拿到MAC進行破壞
- */
- protected static long getDataCenterId(long tempMaxDataCenterId) {
- if (tempMaxDataCenterId < 0L || tempMaxDataCenterId > maxDataCenterId) {
- tempMaxDataCenterId = maxDataCenterId;
- }
- long id = 0L;
- try {
- InetAddress ip = InetAddress.getLocalHost();
- NetworkInterface network = NetworkInterface.getByInetAddress(ip);
- if (network == null) {
- id = 1L;
- } else {
- byte[] mac = network.getHardwareAddress();
- id = ((0x000000FF & (long) mac[mac.length - 1])
- | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
- id = id % (tempMaxDataCenterId + 1);
- }
- } catch (Exception e) {
- System.out.println(" getDatacenterId: " + e.getMessage());
- }
- return id;
- }
- public static void testProductIdByMoreThread(int dataCenterId, int workerId, int n) throws InterruptedException {
- List<Thread> tlist = new ArrayList<>();
- Set<Long> setAll = new HashSet<>();
- CountDownLatch cdLatch = new CountDownLatch(10);
- long start = System.currentTimeMillis();
- int threadNo = dataCenterId;
- Map<String,SnowflakeIdFactory> idFactories = new HashMap<>();
- for(int i=0;i<10;i++){
- //用線程名稱做map key.
- idFactories.put("snowflake"+i,new SnowflakeIdFactory(workerId, threadNo++));
- }
- for(int i=0;i<10;i++){
- Thread temp =new Thread(new Runnable() {
- @Override
- public void run() {
- Set<Long> setId = new HashSet<>();
- SnowflakeIdFactory idWorker = idFactories.get(Thread.currentThread().getName());
- for(int j=0;j<n;j++){
- setId.add(idWorker.nextId());
- }
- synchronized (setAll){
- setAll.addAll(setId);
- log.info("{}生産了{}個id,并成功加入到setAll中.",Thread.currentThread().getName(),n);
- }
- cdLatch.countDown();
- }
- },"snowflake"+i);
- tlist.add(temp);
- }
- for(int j=0;j<10;j++){
- tlist.get(j).start();
- }
- cdLatch.await();
- long end1 = System.currentTimeMillis() - start;
- log.info("共耗時:{}毫秒,預期應該生産{}個id, 實際合并總計生成ID個數:{}",end1,10*n,setAll.size());
- }
- public static void testProductId(int dataCenterId, int workerId, int n){
- SnowflakeIdFactory idWorker = new SnowflakeIdFactory(workerId, dataCenterId);
- SnowflakeIdFactory idWorker2 = new SnowflakeIdFactory(workerId+1, dataCenterId);
- Set<Long> setOne = new HashSet<>();
- Set<Long> setTow = new HashSet<>();
- long start = System.currentTimeMillis();
- for (int i = 0; i < n; i++) {
- setOne.add(idWorker.nextId());//加入set
- }
- long end1 = System.currentTimeMillis() - start;
- log.info("第一批ID預計生成{}個,實際生成{}個<<<<*>>>>共耗時:{}",n,setOne.size(),end1);
- for (int i = 0; i < n; i++) {
- setTow.add(idWorker2.nextId());//加入set
- }
- long end2 = System.currentTimeMillis() - start;
- log.info("第二批ID預計生成{}個,實際生成{}個<<<<*>>>>共耗時:{}",n,setTow.size(),end2);
- setOne.addAll(setTow);
- log.info("合并總計生成ID個數:{}",setOne.size());
- }
- public static void testPerSecondProductIdNums(){
- SnowflakeIdFactory idWorker = new SnowflakeIdFactory(1, 2);
- long start = System.currentTimeMillis();
- int count = 0;
- for (int i = 0; System.currentTimeMillis()-start<1000; i++,count=i) {
- /** 測試方法一: 此用法純粹的生産ID,每秒生産ID個數為300w+ */
- idWorker.nextId();
- /** 測試方法二: 在log中列印,同時擷取ID,此用法生産ID的能力受限于log.error()的吞吐能力.
- * 每秒徘徊在10萬左右. */
- //log.error("{}",idWorker.nextId());
- }
- long end = System.currentTimeMillis()-start;
- System.out.println(end);
- System.out.println(count);
- }
- public static void main(String[] args) {
- /** case1: 測試每秒生産id個數?
- * 結論: 每秒生産id個數300w+ */
- testPerSecondProductIdNums();
- /** case2: 單線程-測試多個生産者同時生産N個id,驗證id是否有重複?
- * 結論: 驗證通過,沒有重複. */
- //testProductId(1,2,10000);//驗證通過!
- //testProductId(1,2,20000);//驗證通過!
- /** case3: 多線程-測試多個生産者同時生産N個id, 全部id在全局範圍内是否會重複?
- * 結論: 驗證通過,沒有重複. */
- /* try {
- testProductIdByMoreThread(1,2,100000);//單機測試此場景,性能損失至少折半!
- } catch (InterruptedException e) {
- e.printStackTrace();
- }*/
- }
- }
本文連結:https://blog.csdn.net/ycb1689/article/details/89331634