/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.common.utils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.util.Assert;

/**
 * <pre>
 * 循环自增长的id，范围为 start<nextId()<end,线程安全的对象
 * </pre>
 * @author 田平 create 2020年11月20日下午6:39:08
 */
public class CycleAtomicInteger {

	private AtomicInteger id;
	private int start = 0;
	private int end = Integer.MAX_VALUE;

	public CycleAtomicInteger() {
		this.id = new AtomicInteger(start);
	}

	public CycleAtomicInteger(int end) {
		this(0, end);
		Assert.isTrue(0 < end, "start must greater than 0,end must greater than start!");
	}

	public CycleAtomicInteger(int start, int end) {
		Assert.isTrue(start > -1 && start < end, "start must greater than 0,end must greater than start!");
		this.start = start;
		this.end = end;
		this.id = new AtomicInteger(start);
	}

	public static void main(String[] args) throws Exception {
		// 并发验证测试

		int start = 0;
		int end = 50001;
		CycleAtomicInteger cycleInteger = new CycleAtomicInteger(start, end);

		ExecutorService es = Executors.newFixedThreadPool(20);

		CountDownLatch cd = new CountDownLatch(end - start - 1);

		Map<Integer, Integer> set = new ConcurrentHashMap<Integer, Integer>(end - start - 1);

		for (int i = 1; i < end; i++) {
			es.execute(() -> {
				int nextId = cycleInteger.nextId();
				set.put(nextId, nextId);
				cd.countDown();
			});
		}
		cd.await();
		System.out.println(set.size());
		System.out.println(set.keySet().stream().mapToInt(key -> key.intValue()).summaryStatistics());
		StopUtil.stopES(es);
	}

	/**
	 * 范围为 start< nextId() <end,线程安全的对象
	 * @return
	 */
	public int nextId() {
		int currId;
		int newId;
		do {
			currId = id.get();
			newId = currId < end - 1 ? currId + 1 : start + 1;
		} while (!id.compareAndSet(currId, newId));
		return newId;
	}

}
