LoginSignup
17
18

More than 5 years have passed since last update.

JAVAでユニークIDを生成するクラス

Last updated at Posted at 2016-03-23

サーバーサイドです。
ユニークIDを作成するときどのような方法で作っていますか?

私は long で値はそのデータが作成された時間としてIDを発行しています。
(下位3桁はミリ秒ではなく、同じ時間での連番になります。)

20110311144634000

これならユニークID生成クラスがシングルトンなら重複してしまうことはありません
もし、ユーザーID内で一意であれば、投稿IDとユーザーID間では重複してもいいというなら、ユニークID生成クラスをそれぞれインスタンス化して使い分けるだけです。

8バイトですが、数値なのでデータベースのインデックスとしても高速に動作し、さらに、IDの値はデータを作成した時間であることから

id = 登録時間、投稿時間

などともう一つの役割を持たせることができます。
なので8バイトと言うデメリットはないかと思います。

デメリットとしては、1つの重複しない環境を用意するとして、1秒間に1000個しかIDを発行できないということです。
ですが、かなーりかなーり人気な掲示板アプリでも1秒間に1000も投稿されることはないでしょうから、Suicaなどよほど大きなシステム出ないかぎり全くもって問題無いでしょう。
(まぁ1000個と言うのは増やせるのですが、昔この3桁にはミリ秒が格納されていたのでその名残です)

それどころか、これはメリットにもなりえます
ありえないかもしれませんが、オートインクリメントでIDを発行した場合、
システム不具合や、DOS攻撃のようなものを受けた時に短時間で一気に枯渇してしまう可能性があります。
この生成方法は、1秒間に生成できる数が決まっているのでIDが枯渇することを防止することができます。

私はこれで数多くのサーバーを作ってきましたがまったくもって問題は発生していません
人気のアプリサーバーにも使いましたが、全く問題なかったです。

UniqueId.java
/**
 * 重複しないユニークなIDを作成する
 * ただしインスタンスが異なるなら重複する
 */
public class UniqueId {
    //  最後に作成したID
    private long lastId = getNow();

    //  絶対に重複しないIDの生成
    //  最大で1秒間に最大で1000のIDが作成できる
    //  返される値は時間だけどミリ秒すなわち下3桁はミリ秒ではない
    //  より多くのIDを生成できるようにその秒でのリクエストで連番を返す
    //  仮に、1000を超えてIDが作成できない時はブロックする
    public synchronized long createId() {
        while (true) {
            long newId = getNow();

            //  前回と同じ秒での取得リクエスト
            //  ミリ秒は連番を返す
            if (newId/1000 == lastId/1000) {
                if (lastId%1000 < 999) {
                    return ++lastId;
                }

                //  連番オーバーフロー
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                }
                continue;
            } else if (newId < lastId) {
                //  サーバーの時間が変わってしまった場合の対処
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
                continue;
            }

            return lastId = newId;
        }
    }

    private static long getNow() {
        Calendar calendar = GregorianCalendar.getInstance();
        long date = calendar.get(Calendar.YEAR)         *   10000000000000l;
        date += (calendar.get(Calendar.MONTH)+1)        *     100000000000l;
        date += calendar.get(Calendar.DAY_OF_MONTH)     *       1000000000l;
        date += calendar.get(Calendar.HOUR_OF_DAY)      *         10000000l;
        date += calendar.get(Calendar.MINUTE)           *           100000;
        date += calendar.get(Calendar.SECOND)           *             1000;
        return date;
    }
}

Thread.sleep(1);
はやり過ぎかな?
まあコンピューターにとってはびくともしないだろうけど
5とかでもいいかもしれないですね。

注意点として、稼働中にサーバーの時間が大きく変わってしまった場合、変わってしまっただけブロックされる可能性があります。
稼働中に1秒サーバーの時間が変わったなら最大1秒このメソッドの実行がブロックされます。

いずれにしても、普通に稼働していればデメリットは発生しないと思われ、問題ないと思います。
(↑のThread.sleep(1);さえ実行されるのはかなり稀でしょうし)

スレッドをスリープさせる時間は計算することもできますが、この方がいいかと思います。
その状況よりも、ブロックしない状況がほとんどなので処理効率的。

まあ、サマータイムさんが導入されると…!??

17
18
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
18