問題:線程不安全,redis
GetNextID中nextIds[BusinessIdKey]爲空,沒有這個鍵
using Consul; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Xml.Serialization; using YesWay.Redis.Base; namespace YesWay.Redis { public class IdGenerator : RedisToolBase { //redis客戶端對象 private static readonly NedisClient client = new NedisClient(GetRedisConfig(redisConfigKey)); //redis客戶端對象配置存放在Consul服務端的key private static readonly string redisConfigKey = "redis/common/idgeneratorconfig"; // 存放BusinessIdKey,MaxId private static readonly Dictionary<string, long> maxIds = new Dictionary<string, long>(); // 存放BusinessIdKey,NextId private static readonly Dictionary<string, long> nextIds = new Dictionary<string, long>(); private static readonly object objincrementsLock = new object(); // 存放BusinessIdKey,id增量 private static readonly Dictionary<string, long> increments = new Dictionary<string, long>(); /// <summary> /// 計算主鍵時的增量 /// </summary> private static readonly uint persistenceIncrement = 10; /// <summary> /// 業務IdKey /// </summary> private string busnessIdKey = string.Empty; /// <summary> /// 使用業務ID的key,ID增量初始化 /// </summary> /// <param name="BusnessIdKey">業務IdKey</param> /// <param name="Increment">id增量,默認爲1,不能大於10</param> public IdGenerator(string BusinessIdKey, uint Increment=1) { Init(BusinessIdKey, Increment); } /// <summary> /// 初始化increments,maxIds,nextIds字典 /// </summary> /// <param name="BusinessIdKey"></param> /// <param name="Increment"></param> private void Init(string BusinessIdKey,long Increment) { if (!increments.ContainsKey(BusinessIdKey)) { lock (objincrementsLock) { if (!increments.ContainsKey(BusinessIdKey)) { increments.Add(BusinessIdKey, Increment); nextIds.Add(BusinessIdKey, maxIds[BusinessIdKey] - persistenceIncrement); maxIds.Add(BusinessIdKey, client.Increment(BusinessIdKey, persistenceIncrement)); } } } } /// <summary> /// 從新設置MaxID /// </summary> /// <returns></returns> private static void ResetMaxID(string BusinessIdKey) { maxIds[BusinessIdKey] = client.Increment(BusinessIdKey, persistenceIncrement); nextIds[BusinessIdKey] = maxIds[BusinessIdKey] - persistenceIncrement; } // 獲取下一個ID的鎖 private static readonly object nextIDLocker = new object(); /// <summary> /// 根據業務Id鍵獲取下一個主鍵ID /// </summary> /// <returns></returns> public Int64 GetNextID(string BusinessIdKey) { lock (nextIDLocker) { nextIds[BusinessIdKey] = nextIds[BusinessIdKey] + 1; // 若是自增後的行程ID大於已經持久的行程ID,則先持久行程ID,再返回 if (nextIds[BusinessIdKey] >= maxIds[BusinessIdKey]) { ResetMaxID(BusinessIdKey); } return nextIds[BusinessIdKey]; } } } }
調用測試代碼:安全
//多線程測試 for (int i = 0; i < 100; i++) { ThreadStart num = new ThreadStart(GeneratorIDTest); Thread numThread = new Thread(num); numThread.Start(); ThreadStart num2 = new ThreadStart(GeneratorIDTest2); Thread numThread2 = new Thread(num2); numThread2.Start(); } //Console.WriteLine("開始" + ids.Count() + "mainID:" + Thread.CurrentThread.ManagedThreadId.ToString()); Thread.Sleep(10000); //Console.WriteLine("結束未去重:" + ids.Count() + "去重:" + ids.Distinct().Count()); Console.WriteLine("safeIds結束未去重:" + safeIds.Count() + "去重:" + safeIds.Distinct().Count()); Console.WriteLine("safeIds1結束未去重:" + safeIds1.Count() + "去重:" + safeIds1.Distinct().Count()); Console.WriteLine("safeIds2結束未去重:" + safeIds2.Count() + "去重:" + safeIds2.Distinct().Count());
mian.cs多線程
static ConcurrentQueue<long> safeIds = new ConcurrentQueue<long>(); static ConcurrentQueue<long> safeIds1 = new ConcurrentQueue<long>(); static ConcurrentQueue<long> safeIds2 = new ConcurrentQueue<long>(); //static Queue<long> safeIds2 = new Queue<long>(); private static void GeneratorIDTest() { var primaryKey = new IdGenerator("blog_id7", 1); for (int i = 0; i < 50; i++) { var id = primaryKey.GetNextID("blog_id7"); //ids.Add(id); safeIds.Enqueue(id); safeIds1.Enqueue(id); Console.WriteLine("線程ID"+Thread.CurrentThread.ManagedThreadId.ToString() +":"+id); } } private static void GeneratorIDTest2() { var primaryKey = new IdGenerator("blog_id8", 1); for (int i = 0; i < 50; i++) { var id = primaryKey.GetNextID("blog_id8"); //ids.Add(id); safeIds.Enqueue(id); safeIds2.Enqueue(id); Console.WriteLine("線程ID" + Thread.CurrentThread.ManagedThreadId.ToString() + ":" + id); } }
錯誤緣由:ide
init方法中只判斷了!increments.ContainsKey(BusinessIdKey)是否包含這個鍵,其它線程繞過,去執行getnext方法了測試
解決辦法:ui
每一個都須要判斷,加鎖,防止其它線程跳過init,去執行getnext方法spa
/// <summary> /// 初始化increments,maxIds,nextIds字典 /// </summary> /// <param name="BusinessIdKey"></param> /// <param name="Increment"></param> private void Init(string BusinessIdKey,long Increment) { if (!increments.ContainsKey(BusinessIdKey)) { lock (objIncrementsLock) { if (!increments.ContainsKey(BusinessIdKey)) { increments.Add(BusinessIdKey, Increment); } } } if (!maxIds.ContainsKey(BusinessIdKey)) { lock (objMaxIdsLock) { if (!maxIds.ContainsKey(BusinessIdKey)) { maxIds.Add(BusinessIdKey, client.Increment(BusinessIdKey, persistenceIncrement)); } } } if (!nextIds.ContainsKey(BusinessIdKey)) { lock (objNextIdsLock) { if (!nextIds.ContainsKey(BusinessIdKey)) { nextIds.Add(BusinessIdKey, maxIds[BusinessIdKey] - persistenceIncrement); } } } }