背景
如果是在单机系统中实现自增流水号,那么使用AtomicLong 实现是很方便的。
优点
- AtomicLong 在内存中实现自增,所以效率高,资源消耗小
- AtomicLong 本身是线程安全的,不需要额外的实现线程安全的代码
缺点
- 不适合分布式系统
- 程序重启会丢失已使用的序号
- AtomicLong 会一直自增,如果流水号限制在6位,或6位以下,那就流水号会有溢出的问题、
6位流水号最大值是999999,AtomicLong 不会在达到999999时,自动重新从1开始计数
循环自增流水号功能
下面我们一步步实现循环自增流水号功能,并解决第二个和第三个问题。至于第一个问题,建议百度找一个分布式流水号使用。
基础实现
import java.util.concurrent.atomic.AtomicLong;
/**
* @create 2023/12/25 11:04
*/
public class AtomicIncrementer {
/**
* 定义一个AtomicLong实例,初始值为1
*/
private AtomicLong atomicLong = new AtomicLong(1);
/**
* 主方法
* 获取当前值,并自增1
* @return
*/
public Long nextLong() {
//getAndIncrement 是线程安全的
return atomicLong.getAndIncrement();
}
/**
* 获取字符串型流水号
* @return
*/
public String nextString() {
Long sequence = this.nextLong();
return sequence + "";
}
/**
* 获取字整形型流水号
* @return
*/
public Integer nextInt() {
Long sequence = this.nextLong();
return sequence.intValue();
}
}
重启程序不丢失
上面的实现,很明显当程序重启后,序号又重1开始了。为了解决这个问题,我们在程序退出时,把已使用的序号保存到文件里。程序启动时从文件里取到已使用的序号当做起始值。
import cn.com.soulfox.common.utils.file.TxtFileReader;
import cn.com.soulfox.common.utils.file.TxtFileWriter;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Component;
/**
* @create 2023/12/25 11:04
*/
@Component
public class AtomicIncrementer {
/**
* 定义一个AtomicLong
*/
private AtomicLong atomicLong = null;
/**
* 文件路径
* 可以从配置文件获取,这里就写死了
*/
private String flownoFilePath = "./flowno/flowno-record.txt";
@PostConstruct //spring容器实例对象后,自动执行本方法
public void init(){
File flownoFile = new File(flownoFilePath);
if(flownoFile.exists()){
//文件存在,从文件读取序号值
long historyFlowNo = read(flownoFile);
//初始化 atomicLong 对象
atomicLong = new AtomicLong(historyFlowNo);
}else {
//文件不存在
String dir = flownoFile.getParent();
File dirFile = new File(dir);
if(!dirFile.exists()){
//目录不存在,创建目录
dirFile.mkdirs();
}
//初始化 atomicLong 对象,从1开始
atomicLong = new AtomicLong(1L);
//写入文件
write(flownoFile, 1L);
}
//钩子线程,程序退出前会执行本线程
//在linux环境里,使用 “kill 进程号” 杀进程,不要用“kill -9 进程号”
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
//在程序退出前执行,把已使用序号,写入文件
write(flownoFile, atomicLong.longValue());
}));
}
/**
* 主方法
* 获取当前值,并自增1
* @return
*/
public Long nextLong() {
//getAndIncrement 是线程安全的
return atomicLong.getAndIncrement();
}
/**
* 获取字符串型流水号
* @return
*/
public String nextString() {
Long sequence = this.nextLong();
return sequence + "";
}
/**
* 获取字整形型流水号
* @return
*/
public Integer nextInt() {
Long sequence = this.nextLong();
return sequence.intValue();
}
private Long read(File flownoFile) {
TxtFileReader txtFileReader = null;
try {
txtFileReader = new TxtFileReader(flownoFile);
String line = txtFileReader.readLine();
if(line == null || line.length() == 0){
return 0L;
}
return Long.parseLong(line.trim());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(txtFileReader != null){
txtFileReader.close();
}
}
return 0L;
}
private void write(File flownoFile, Long currFlowNo) {
TxtFileWriter txtFileWriter = null;
try {
txtFileWriter = new TxtFileWriter(flownoFile);
txtFileWriter.writeLineLn(currFlowNo+"");
txtFileWriter.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(txtFileWriter != null){
txtFileWriter.close();
}
}
}
}
TxtFileWriter 类 参考用,就是往文件里写个字符串
import java.io.*;
import java.sql.Timestamp;
import java.util.List;
/**
* 写文本文件
* @create 2012-03-21 10:44
*/
public class TxtFileWriter {
private File file;
private OutputStreamWriter osw;
private FileOutputStream fos;
private BufferedWriter bw;
private static final String DEFAULT_CHARSET_UTF8 = "UTF-8";
/**
* 使用默认编码(utf-8)构造写文件对象
* @param fullFileName
* @throws FileNotFoundException
* @throws UnsupportedEncodingException
*/
public TxtFileWriter(File file) throws FileNotFoundException, UnsupportedEncodingException{
this.file = file;
this.fos = new FileOutputStream(file);
this.osw = new OutputStreamWriter(fos, DEFAULT_CHARSET_UTF8 ); //使用指定编码写文件
this.bw = new BufferedWriter(osw);
}
/**
* 写入一行数据, 并换行
* @param line 一行内容
* @throws IOException
*/
public void writeLineLn(String line) throws IOException{
this.bw.write(line);
this.bw.newLine();
}
/**
* 刷新缓存, 把缓存中的内容写到文件里,并清空缓存
* @throws IOException
*/
public void flush() throws IOException{
this.bw.flush();
}
/**
* 关闭文件流
* @throws IOException
*/
public void close(){
if (this.bw != null) {
try {
this.bw.close();
} catch (IOException e) {
}
}
if (this.bw != null) {
try {
this.osw.close();
} catch (IOException e) {
}
}
if (this.fos != null) {
try {
this.fos.close();
} catch (IOException e) {
}
}
}
}
TxtFileReader 类 参考用,从文件里读取一行
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 读文本文件
* @author jiyh
* @create 2012-03-21 13:45
*/
public class TxtFileReader {
private File file;
private FileInputStream fis;
private InputStreamReader isr;
private BufferedReader br;
public TxtFileReader(){
}
/**
*
* @param fullName
* @throws IOException
*/
public TxtFileReader(File file) throws IOException{
this.file = file;
this.fis = new FileInputStream(file);
this.isr = new InputStreamReader(fis);
this.br = new BufferedReader(isr);
}
/**
* 读一行数据
* @return
* @throws IOException
*/
public String readLine() throws IOException{
return this.br.readLine();
}
/**
* 关闭文件流
* @throws IOException
*/
public void close(){
if (this.br != null) {
try {
this.br.close();
} catch (IOException e) {
}
}
if (this.isr != null) {
try {
this.isr.close();
} catch (IOException e) {
}
}
if (this.fis != null) {
try {
this.fis.close();
} catch (IOException e) {
}
}
}
}
达到最大值重置流水号
import cn.com.soulfox.common.utils.file.TxtFileReader;
import cn.com.soulfox.common.utils.file.TxtFileWriter;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Component;
/**
* @create 2023/12/25 11:04
*/
@Component
public class AtomicIncrementer {
/**
* 定义一个AtomicLong实例,初始值为1
*/
private AtomicLong atomicLong = null;
/**
* 文件路径
* 可以从配置文件获取,这里就写死了
*/
private String flownoFilePath = "./flowno/flowno-record.txt";
/**
* 最大流水号,
* 可以从配置文件获取,这里就写死6位
*/
private Long maxFlowno = 999999L;
/**
* 用于synchronized关键字的锁对象
*/
private Object locker = new Object();
@PostConstruct //实现后,自动执行
public void init(){
File flownoFile = new File(flownoFilePath);
if(flownoFile.exists()){
//文件存在,从文件读取序号值
long historyFlowNo = read(flownoFile);
//初始化 atomicLong 对象
atomicLong = new AtomicLong(historyFlowNo);
}else {
//文件不存在
String dir = flownoFile.getParent();
File dirFile = new File(dir);
if(!dirFile.exists()){
//目录不存在,创建目录
dirFile.mkdirs();
}
//初始化 atomicLong 对象,从1开始
atomicLong = new AtomicLong(1L);
//写入文件
write(flownoFile, 1L);
}
//钩子线程,程序退出前执行
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
//在程序退出前执行,把已使用序号,写入文件
write(flownoFile, atomicLong.longValue());
}));
}
/**
* 主方法
* 获取当前值,并自增1
* 超过最大流水号,重置序号
* @return
*/
public Long nextLong() {
//getAndIncrement 是线程安全的
long currValue = atomicLong.getAndIncrement() ;
if(currValue > maxFlowno){
//序号值大于配置值,让序号重头开始
synchronized (this.locker){
//再检查一次当前值是否大于配置值,防止有其他线程已修改当前值
//如果不大于,则表示已经有线程重置了atomicInteger对像,现在取到的值是正确的
currValue = atomicLong.getAndIncrement() ;
if(currValue > maxFlowno){
//重新从1开始
AtomicLong tmp = new AtomicLong(1L);
//复制引用比创建对象块
atomicLong = tmp;
//获取序列值
currValue = atomicLong.getAndIncrement();
}
}
}
return currValue;
}
/**
* 获取字符串型流水号
* @return
*/
public String nextString() {
Long sequence = this.nextLong();
return sequence + "";
}
/**
* 获取字整形型流水号
* @return
*/
public Integer nextInt() {
Long sequence = this.nextLong();
return sequence.intValue();
}
private Long read(File flownoFile) {
TxtFileReader txtFileReader = null;
try {
txtFileReader = new TxtFileReader(flownoFile);
String line = txtFileReader.readLine();
if(line == null || line.length() == 0){
return 0L;
}
return Long.parseLong(line.trim());
} catch (IOException e) {
e.printStackTrace();
}finally {
if(txtFileReader != null){
txtFileReader.close();
}
}
return 0L;
}
private void write(File flownoFile, Long currFlowNo) {
TxtFileWriter txtFileWriter = null;
try {
txtFileWriter = new TxtFileWriter(flownoFile);
txtFileWriter.writeLineLn(currFlowNo+"");
txtFileWriter.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(txtFileWriter != null){
txtFileWriter.close();
}
}
}
}