JAVA AtomicLong 实现循环自增流水号

背景

  如果是在单机系统中实现自增流水号,那么使用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();
            }

        }

    }
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值