私は、呼び出されたときにシステム時刻を使用して参照IDとして一意の8文字の英数字を生成するメソッドを呼び出すクラスを作成しています。しかし、ある時点で、同じミリ秒で複数の呼び出しが行われ、同じ参照IDが生成される可能性があるのではないかと心配しています。このメソッドを同時に呼び出す可能性のある複数のスレッドからシステム時刻へのこの呼び出しを保護するにはどうすればよいですか?
3 に答える
システム時刻は、固有 ID の信頼できないソースです。それでおしまい。使用しないでください。何らかの形式の永続的なソースが必要です (UUID は、OS によって提供されるセキュア ランダムを使用します)
システム時間は、数ミリ秒でも後退/ジャンプして、ロジックを完全に台無しにする可能性があります。64ビットのみを許容できる場合は、非常に優れた妥協点であるHigh / Lowジェネレーターを使用するか、独自のレシピを調理することができます. SecureRandom - 最良のケースではなく、技術的には失敗する可能性がありますが、外部永続性は必要ありません。
threadIDを参照 IDに追加することをお勧めします。これにより、参照がより一意になります。ただし、スレッド内であっても、タイム ソースへの連続した呼び出しで同じ値が返される場合があります。最高解像度のソース ( QueryPerformanceCounter ) を呼び出しても、特定のハードウェアでは同じ値になる場合があります。この問題の可能な解決策は、収集された時間値をその前の時間値と比較してテストし、増分項目を「タイムスタンプ」に追加することです。人間が判読できるようにする必要がある場合は、8 文字以上が必要になる場合があります。タイムスタンプの最も効率的なソースは、GetSystemTimeAsFileTime API です。この回答に詳細を書きました。
このUUID
クラスを使用して ID のビットを生成し、ビット単位の演算子を使用Long.toString
して base-36 (英数字) に変換できます。
public static String getId() {
UUID uuid = UUID.randomUUID();
// This is the time-based long, and is predictable
long msb = uuid.getMostSignificantBits();
// This contains the variant bits, and is random
long lsb = uuid.getLeastSignificantBits();
long result = msb ^ lsb; // XOR
String encoded = Long.toString(result, 36);
// Remove sign if negative
if (result < 0)
encoded = encoded.substring(1, encoded.length());
// Trim extra digits or pad with zeroes
if (encoded.length() > 8) {
encoded = encoded.substring(encoded.length() - 8, encoded.length());
}
while (encoded.length() < 8) {
encoded = "0" + encoded;
}
}
文字スペースは に比べてまだ小さいためUUID
、これは確実ではありません。次のコードでテストします。
public static void main(String[] args) {
Set<String> ids = new HashSet<String>();
int count = 0;
for (int i = 0; i < 100000; i++) {
if (!ids.add(getId())) {
count++;
}
}
System.out.println(count + " duplicate(s)");
}
100,000 個の ID の場合、コードはかなり一貫して適切に実行され、非常に高速です。もう 1 桁増やして 1,000,000 にすると、ID が重複し始めます。エンコードされた文字列の先頭ではなく末尾を取得するようにトリミングを変更したところ、重複 ID 率が大幅に改善されました。現在、1,000,000 個の ID を持っていても重複はありません。
特に多数の ID を使用する予定がある場合は、上記のコードを使用して、AtomicInteger
またはのような同期カウンターを使用し、base-36 で数値をエンコードすることをお勧めします。AtomicLong
編集:必要に応じて、カウンターアプローチ:
private final AtomicLong counter;
public IdGenerator(int start) {
// start could also be initialized from a file or other
// external source that stores the most recently used ID
counter = new AtomicLong(start);
}
public String getId() {
long result = counter.getAndIncrement();
String encoded = Long.toString(result, 36);
// Remove sign if negative
if (result < 0)
encoded = encoded.substring(1, encoded.length());
// Trim extra digits or pad with zeroes
if (encoded.length() > 8) {
encoded = encoded.substring(0, 8);
}
while (encoded.length() < 8) {
encoded = "0" + encoded;
}
}
このコードはスレッドセーフであり、同時にアクセスできます。