All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.norconex.commons.lang.TimeIdGenerator Maven / Gradle / Ivy

/* Copyright 2015 Norconex Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.norconex.commons.lang;

import java.math.BigDecimal;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

/**
 * 

* Generates a unique ID made out of the current time in milliseconds, * combined with a thread-safe atomic sequence value that guarantees * order and uniqueness within the same JVM. *

*

* This class was created for cases where unique sequential IDs are required * to be {@code long} primitives, * or where {@code long} are desired for things such as faster lookup * and generation (over large strings for instance) and you can't use a * persistent sequence generator. * If you do not have such need, it is best advised to use a * universal id generator instead, like java {@link UUID}. *

* *

The fine-prints

*

* At a minimum, values generated by this * class ensures uniqueness within JVM instances (read * Year 2262 and beyond further down). * If you need larger or different values to ensure greater * uniqueness between many JVMs, * you can convert the returned value to a string and simply * prefix it with a value of your choice (or convert to a {@code BigInteger} * if your can support larger numbers). * To obtain universally unique IDs, consider using a UUID implementation, * like the Java {@link UUID}. *

*

* IDs generated within a single JVM are guaranteed to be in order with no * duplicates (read * Year 2262 and beyond further down). * The sequence usually has many gaps. *

*

* Generated IDs are {@code long} primitives. Java {@code long} values * are 64-bit whereas standard UUID are byte arrays of 128-bit * (plus the array reference itself). When using UUIDs in their common * hexadecimal string format, they usually are at least 576-bit (plus the * string reference). *

*

* The reduced byte size of {@code long} values compared to most strings * may lead to lookup performance, in * addition of some system sorting primitive numbers faster than strings. * While not especially optimized for ID generation, creating {@code long}-based * IDs is much faster than most string-based approach. For instance, this class * is typically more than 20 times faster at generating {@code long} values * than Java UUID at generating strings. * An average desktop computer can show it takes less than 50 milliseconds to * generate 1 million IDs (single thread). *

*

* Be advised this implementation does not account for the possibility of * a backward UTC time change on the host system clock (as opposed to * "local" time which can change without issues as long as UTC time is * unaffected). *

*

* Assuming your application and underlying platform can * achieve this feat, a maximum of 1 million unique IDs can be generated every * milliseconds (1 billion IDs per seconds). Every time that threshold is * reached, the method will wait until the current time has progressed * to the next millisecond to prevent ID duplication (waiting 1 nanosecond * at a time). *

*

* Java {@code long} values can hold 19 characters. Each digit is part * of one of two groups of digits with a specific purpose. * The exact pattern is the following: *

*
 * Java long max value:   9223372036854775808
 * ------------------------------------------
 * Current time (ms):     9223372036854          <--- max value
 * Atomic sequence:                    999999    <--- max value
 * 
*

* This class is thread-safe. *

* * *

Year 2262 and beyond

*

* Whenever the millisecond EPOCH representation of the current time reaches * 1e14, the time representation starts back at 0ms using a modulo between * the current time and 1e14. The first time this will occur is when the * system UTC clock time reaches April 11th, 2262. The moment * rollback of the current time value occurs, new IDs will be smaller * {@code long} values than IDs generated prior to that date. * If you only care about uniqueness, your IDs will still be unique unless * you have been generating IDs using this class for close to 300 years or more. *

* * @author Pascal Essiembre * @since 1.6.0 */ public final class TimeIdGenerator { private static final Logger LOG = LogManager.getLogger(TimeIdGenerator.class); private static final AtomicInteger DUP_SEQUENCE = new AtomicInteger(); private static final long MILLIS_ROLLOVER_VALUE = BigDecimal.valueOf(1e14).longValueExact(); private static final int MAX_DUP_SEQUENCE = 999999; private static final int TIME_MULTIPLIER = 1000000; private static long previousTime = -1; private static long previousGeneratedId = -1; private static int previousGeneratedDupSequence = 0; private TimeIdGenerator() { super(); } /** * Returns the last generated number since the start of this * JVM (that value is not persisted anywhere outside the JVM memory). * Invoking this method before having called {@link #next()} at least * once will return {@code -1}. * @return the last id generated */ public synchronized static long last() { return previousGeneratedId; } /** * Generates a new number unique within this JVM. * @return a long value */ public synchronized static long next() { long time = System.currentTimeMillis() % MILLIS_ROLLOVER_VALUE; long id = time * TIME_MULTIPLIER; id += getDupSequence(time == previousTime); previousTime = time; previousGeneratedId = id; return id; } private static int getDupSequence(boolean needsIncrement) { if (needsIncrement) { int dupSeq = DUP_SEQUENCE.incrementAndGet(); previousGeneratedDupSequence = dupSeq; if (dupSeq == MAX_DUP_SEQUENCE) { while (System.currentTimeMillis() == previousTime) { Sleeper.sleepNanos(1); } if (LOG.isDebugEnabled()) { LOG.debug("Reached " + (MAX_DUP_SEQUENCE + 1) + " ID creations in one millisecond. " + "Had to wait for current millisecond to pass."); } } return dupSeq; } else { if (previousGeneratedDupSequence != 0) { DUP_SEQUENCE.set(0); previousGeneratedDupSequence = 0; } return 0; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy