`
ivan19861025
  • 浏览: 41943 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Java并发编程-生成唯一序列号

    博客分类:
  • Java
 
阅读更多

所用到的并发编程库

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

 

package com.league.idgenerate;

/**
 * 
 * ID生成器接口, 用于生成全局唯一的ID流水号
 * 
 * @author Ivan.Ma
 */
public interface IdGenerator {
	
	/**
	 * 生成下一个不重复的流水号
	 * @return
	 */
	String next();
	
}

 

package com.league.idgenerate;

/**
 * ID生成器的配置接口
 * @author Ivan.Ma
 */
public interface IdGeneratorConfig {
	
	/**
	 * 获取分隔符
	 * @return
	 */
	String getSplitString();
	
	/**
	 * 获取初始值
	 * @return
	 */
	int getInitial(); 
	
	/**
	 * 获取ID前缀
	 * @return
	 */
	String getPrefix();
	
	/**
	 * 获取滚动间隔, 单位: 秒
	 * @return
	 */
	int getRollingInterval();
	
}

 

package com.league.idgenerate;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;


/**
 * 默认的ID生成器, 采用前缀+时间+原子数的形式实现
 * 建议相同的配置采用同一个实例
 * @see IdGeneratorConfig
 * @author Ivan.Ma
 */
public class DefaultIdGenerator implements IdGenerator, Runnable{
	
	private String time;
	
	private AtomicInteger value;
	
	private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
	
	private IdGeneratorConfig config;
	
	private Thread thread;
	
	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	
	public DefaultIdGenerator(){
		config = new DefaultIdGeneratorConfig();
		time = LocalDateTime.now().format(FORMATTER);
		value = new AtomicInteger(config.getInitial());
		
		thread = new Thread(this);
		thread.setDaemon(true);
		thread.start();
	}
	
	public DefaultIdGenerator(IdGeneratorConfig config){
		this.config = config;
		time = LocalDateTime.now().format(FORMATTER);
		value = new AtomicInteger(config.getInitial());
		
		thread = new Thread(this);
		thread.setDaemon(true);
		thread.start();
	}
	
	@Override
	public String next() {
		lock.readLock().lock();
		StringBuffer sb = new StringBuffer(config.getPrefix()).append(config.getSplitString()).append(time).append(config.getSplitString()).append(value.getAndIncrement());
		lock.readLock().unlock();
		return sb.toString();
	}
	
	@Override
	public void run() {
		while (true){
			try {
				Thread.sleep(1000 * config.getRollingInterval());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			String now = LocalDateTime.now().format(FORMATTER);
			if (!now.equals(time)){
				lock.writeLock().lock();
				time = now;
				value.set(config.getInitial());
				lock.writeLock().unlock();
			}
		}
	}
	
}

 

package com.league.idgenerate;

public class DefaultIdGeneratorConfig implements IdGeneratorConfig{

	@Override
	public String getSplitString() {
		return "";
	}

	@Override
	public int getInitial() {
		return 1;
	}

	@Override
	public String getPrefix() {
		return "";
	}

	@Override
	public int getRollingInterval() {
		return 1;
	}

}

 测试类, 该类主要演示如何使用及相关测试功能代码,如下:

package com.league.idgenerate;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

/**
 * 用法说明
 * @author Ivan.Ma
 */
public class TestStandaloneIdGenerator {
	
	@Test
	public void test1(){
		IdGenerator idGenerator = new DefaultIdGenerator();
		
		System.out.println("--------简单测试------------------");
		for (int i=0; i<100; i++){
			System.out.println(idGenerator.next());
		}
	}
	
	@Test
	public void test2(){
		IdGenerator idGenerator = new DefaultIdGenerator();
		
		//多线程测试
		System.out.println("--------多线程测试不重复------------------");
		Set<String> idSet = Collections.synchronizedSet(new HashSet<>());
		ExecutorService es = Executors.newFixedThreadPool(100);
		for (int i=0; i<2000000; i++){
			es.submit(() -> {
				String val = idGenerator.next();
				if (idSet.contains(val)){
					System.out.println("重复了: " + val);
				}else{
					idSet.add(val);
				}
			});
		}
		es.shutdown();
		System.out.println("启用顺序关闭");
		while(true){  
            if(es.isTerminated()){
                System.out.println("所有的子线程都结束了!");  
                break;
            }
            try {
            	System.out.println("子线程的任务还没运行完");
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
        }  
		System.out.println("共生成: " + idSet.size() + "个");
	}
	
	@Test
	public void  test3(){
		//测试单机性能
		System.out.println("--------测试单线程性能------------------");
		IdGenerator idGenerator2 = new DefaultIdGenerator();
		long t1 = System.currentTimeMillis();
		int total = 10000000;
		for (int i=0; i<total; i++){
			idGenerator2.next();
		}
		System.out.println("单线程生成" + total + "个ID共耗时: " + (System.currentTimeMillis() - t1) + "ms");
	}
	
	//500个线程并发, 每个线程获取10000个ID
	@Test
	public void test4(){
		//测试多线程性能
		System.out.println("--------测试多线程性能------------------");
		ExecutorService es1 = Executors.newFixedThreadPool(500);
		IdGenerator idGenerator3 = new DefaultIdGenerator();
		long t1 = System.currentTimeMillis();
		for (int i=0; i<500; i++){
			es1.submit(() -> {
				int count = 0;
				while (count < 10000){
					idGenerator3.next();
					
					count++;
				}
			});
		}
		es1.shutdown();
		System.out.println("启用顺序关闭");
		while(true){  
            if(es1.isTerminated()){
                System.out.println("所有的子线程都结束了!");  
                break;
            }
            try {
            	System.out.println("子线程的任务还没运行完");
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
        }  
		System.out.println("500线程,每个线程生成10000个序列号.共耗时: " + (System.currentTimeMillis() - t1) + " ms");
	}
	
	@Test
	public void test5(){
		System.out.println("--------测试生成的ID是否有时间滚动----------");
		IdGenerator idGenerator = new DefaultIdGenerator();
		for (int i=0; i<20; i++){
			String id = idGenerator.next();
			System.out.println(id);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	@Test
	public void test6(){
		System.out.println("--------ID生成器的特殊设置相关----------");
		IdGeneratorConfig config = new DefaultIdGeneratorConfig() {
			@Override
			public String getSplitString() {
				return "-";
			}
			@Override
			public int getInitial() {
				return 1000000;
			}
			@Override
			public String getPrefix() {
				return "NODE01";
			}
		};
		IdGenerator idGenerator = new DefaultIdGenerator(config);
		for (int i=0; i<20; i++){
			String id = idGenerator.next();
			System.out.println(id);
			try {
				Thread.sleep(1000 * 1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 运行结果如下:

--------简单测试------------------
201602031124321
201602031124322
201602031124323
201602031124324
201602031124325
201602031124326
201602031124327
201602031124328
201602031124329
2016020311243210
2016020311243211
2016020311243212
2016020311243213
2016020311243214
2016020311243215
2016020311243216
2016020311243217
2016020311243218
2016020311243219
2016020311243220
2016020311243221
2016020311243222
2016020311243223
2016020311243224
2016020311243225
2016020311243226
2016020311243227
2016020311243228
2016020311243229
2016020311243230
2016020311243231
2016020311243232
2016020311243233
2016020311243234
2016020311243235
2016020311243236
2016020311243237
2016020311243238
2016020311243239
2016020311243240
2016020311243241
2016020311243242
2016020311243243
2016020311243244
2016020311243245
2016020311243246
2016020311243247
2016020311243248
2016020311243249
2016020311243250
2016020311243251
2016020311243252
2016020311243253
2016020311243254
2016020311243255
2016020311243256
2016020311243257
2016020311243258
2016020311243259
2016020311243260
2016020311243261
2016020311243262
2016020311243263
2016020311243264
2016020311243265
2016020311243266
2016020311243267
2016020311243268
2016020311243269
2016020311243270
2016020311243271
2016020311243272
2016020311243273
2016020311243274
2016020311243275
2016020311243276
2016020311243277
2016020311243278
2016020311243279
2016020311243280
2016020311243281
2016020311243282
2016020311243283
2016020311243284
2016020311243285
2016020311243286
2016020311243287
2016020311243288
2016020311243289
2016020311243290
2016020311243291
2016020311243292
2016020311243293
2016020311243294
2016020311243295
2016020311243296
2016020311243297
2016020311243298
2016020311243299
20160203112432100
--------多线程测试不重复------------------
启用顺序关闭
子线程的任务还没运行完
子线程的任务还没运行完
子线程的任务还没运行完
子线程的任务还没运行完
子线程的任务还没运行完
所有的子线程都结束了!
共生成: 2000000个
--------测试单线程性能------------------
单线程生成10000000个ID共耗时: 1972ms
--------测试多线程性能------------------
启用顺序关闭
子线程的任务还没运行完
子线程的任务还没运行完
子线程的任务还没运行完
子线程的任务还没运行完
子线程的任务还没运行完
子线程的任务还没运行完
子线程的任务还没运行完
所有的子线程都结束了!
500线程,每个线程生成10000个序列号.共耗时: 1605 ms
--------测试生成的ID是否有时间滚动----------
201602031124431
201602031124432
201602031124433
201602031124434
201602031124435
201602031124436
201602031124437
201602031124438
201602031124439
2016020311244310
2016020311244311
201602031124441
201602031124442
201602031124443
201602031124444
201602031124445
201602031124446
201602031124447
201602031124448
201602031124449
--------ID生成器的特殊设置相关----------
NODE01-20160203112445-1000000
NODE01-20160203112445-1000001
NODE01-20160203112446-1000000
NODE01-20160203112447-1000000
NODE01-20160203112448-1000000
NODE01-20160203112449-1000000
NODE01-20160203112450-1000000
NODE01-20160203112452-1000000
NODE01-20160203112453-1000000
NODE01-20160203112454-1000000
NODE01-20160203112455-1000000
NODE01-20160203112456-1000000
NODE01-20160203112457-1000000
NODE01-20160203112458-1000000
NODE01-20160203112459-1000000
NODE01-20160203112500-1000000
NODE01-20160203112501-1000000
NODE01-20160203112502-1000000
NODE01-20160203112503-1000000
NODE01-20160203112504-1000000

 从该性能来看, 可以使用在高并发的场景, 欢迎大家来拍砖!

 

0
1
分享到:
评论
5 楼 ivan19861025 2016-02-24  
ivan19861025 写道
Saro 写道
提醒一下,SimpleDateFormat不是线程安全的。

* Date formats are not synchronized.
* It is recommended to create separate format instances for each thread.
* If multiple threads access a format concurrently, it must be synchronized
* externally.


我并没有改变simpledateformat内部状态, 有什么问题呢?

看了源代码,simpledateformat内部使用了calendar, 确实有线程安全的问题.更改为java8的time相关api实现
4 楼 ivan19861025 2016-02-16  
Saro 写道
提醒一下,SimpleDateFormat不是线程安全的。

* Date formats are not synchronized.
* It is recommended to create separate format instances for each thread.
* If multiple threads access a format concurrently, it must be synchronized
* externally.


我并没有改变simpledateformat内部状态, 有什么问题呢?
3 楼 ivan19861025 2016-02-16  
dieslrae 写道
你就没考虑过集群环境?

集群有什么问题呢? 集群用前面的节点来区分.保证全局唯一
2 楼 dieslrae 2016-02-03  
你就没考虑过集群环境?
1 楼 Saro 2016-02-03  
提醒一下,SimpleDateFormat不是线程安全的。

* Date formats are not synchronized.
* It is recommended to create separate format instances for each thread.
* If multiple threads access a format concurrently, it must be synchronized
* externally.

相关推荐

    java生成流水-格式202001270001

    在Java编程中,生成流水号是一项常见的需求,特别是在金融、电商等系统中,流水号作为交易的唯一标识,对于数据的追踪和管理至关重要。在这个场景中,“java生成流水-格式202001270001”指的是一个Java程序,用于...

    java生成数据库表序列号

    在Java编程中,生成数据库表序列号通常涉及到数据库序列(Sequence)的概念,这是数据库系统用于自动为表生成唯一标识符的一种机制。Oracle、PostgreSQL等数据库支持序列,而MySQL等数据库可能通过自增主键来实现...

    数据库序列号生成器

    1. **序列号生成算法**:生成序列号的算法可以是简单的自增、随机数生成、时间戳加盐(确保时间相关的唯一性)或者是更复杂的分布式ID生成算法,如Twitter的Snowflake算法,它考虑了多节点环境下的分布式一致性。...

    java生成唯一id(uuid)(不依赖jdk5,自己实现的)

    在Java编程中,生成唯一的ID(Universal Unique Identifier,UUID)是一项常见的需求,特别是在数据库记录、分布式系统中的对象标识等方面。UUID是一种128位的数字,通常以32个字符的16进制形式表示,确保在全球范围...

    java生成申请单序列号的实现方法

    在Java编程中,生成申请单序列号是一项常见的需求,它通常要求序列号具有一定的规则,如包含日期信息和连续的序列。在这个问题中,我们看到一个实现此类功能的示例代码,该代码利用了锁机制来确保序列号的正确生成。...

    java产生序列号源码

    在Java编程中,序列号(Serial Number)通常用于标识唯一对象或者进行数据持久化时保持对象的状态。生成序列号源码涉及的主要知识点包括字符串处理、随机数生成、日期时间操作以及可能的加密算法。以下是对这些知识...

    内存生成公用的订单编号序列号

    1. **序列号生成器类**:定义一个类,负责生成序列号。它可能包含一个原子变量来存储当前序列号,并提供一个`generate()`方法用于获取新的序列号。 2. **并发控制**:为了保证多线程环境下的安全性,可能使用`...

    java生成UUID通用唯一识别码 中文WORD版

    在Java中,UUID类提供了生成UUID的能力,这对于分布式系统中的唯一标识或者序列号生成等场景非常有用。 在Java中,UUID主要通过`java.util.UUID`类来实现。这个类提供了多种生成UUID的方法,包括最常用的`...

    java快速ID自增器

    在Java开发中,高效地生成唯一且自增的ID是许多系统设计的关键部分。"Java快速ID自增器"就是为了解决这个问题而设计的一种工具或解决方案。它旨在提供一个高性能、线程安全的方式来生成自增ID,尤其适用于那些需要...

    订单号生成工具类

    7. **工具类的设计**:在Java等编程语言中,订单号生成工具类通常会设计为线程安全,提供静态方法供调用,以确保在多线程环境下的正确性。同时,可以考虑提供配置项,允许用户自定义订单号的格式和生成策略。 8. **...

    java实现数据库主键生成示例

    在Java编程中,数据库主键生成是一个常见的需求,特别是在创建数据表记录时,主键用于唯一标识每条记录。这个示例将展示如何使用Java来动态生成数据库主键,确保其唯一性。以下是对给定代码的详细解释和扩展。 首先...

    根据当天日期自动生成单据号

    在IT行业中,尤其是在Java编程领域,自动生成单据号是一个常见的需求,特别是在财务系统、订单管理系统或者任何需要唯一标识业务实体的地方。这个需求通常涉及到日期处理、字符串格式化以及可能的序列号生成。下面...

    自动生成主键uuid.zip

    在Java编程中,UUID(Universally Unique Identifier)是一种标准的128位的全局唯一标识符,用于生成不可预测、全局唯一的ID。UUID通常由32个16进制数字组成,中间用短横线分隔,形如“8-4-4-4-12”的格式。然而,在...

    java多线程中的原子操作

    在Java多线程编程中,原子操作是一种非常关键的概念,它涉及到并发控制和线程安全。原子操作是指在不被其他线程中断的情况下,能够完整执行的一个或一系列操作。这样的操作在多线程环境中可以保证数据的一致性和完整...

    Java坦克网络游戏对战版源代码

    【Java坦克网络游戏对战版源代码】是一款基于Java开发的多人在线对战游戏,它展现了Java在网络编程领域的强大能力和灵活性。这款源代码提供了完整的坦克大战游戏逻辑,包括玩家的控制、坦克移动、射击、碰撞检测以及...

    Java高级互联网MCA架构师 美团leaf源码分析

    `Sequence`类则是每个节点内部生成序列号的逻辑,它通常会采用原子操作或乐观锁等机制保证线程安全。 深入源码分析,我们可以看到Leaf如何通过`SegmentService`进行段的申请和更新。在获取新段时,`SegmentService`...

    20191220-Java位运算_java_位运算_

    序列号则用于同一节点在同一时间片内的微调,确保并发下的唯一性。 10. **雪花算法中的位运算应用** - 时间戳:通过左移将时间戳的二进制表示放置在高位,例如左移41位,以便容纳69年的时间跨度。 - 工作机器ID:...

    xsequence-master.zip

    开发此类组件通常涉及编程语言(如Java、Python或Go)、版本控制(Git)、构建工具(Maven、Gradle)、持续集成/持续部署(CI/CD)工具(Jenkins、Travis CI)等。部署时可能需要考虑负载均衡、故障恢复和监控报警...

    SelectRepeat选择重传的java实现

    总的来说,"SelectRepeat选择重传的Java实现"是一个涉及网络通信协议、并发编程和数据结构设计的项目。通过这个项目,开发者可以深入理解网络协议的实现细节,同时提高Java编程技巧和对系统级问题的解决能力。在实际...

Global site tag (gtag.js) - Google Analytics