问题:线程不安全,
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);
}
}
错误原因:
init方法中只判断了!increments.ContainsKey(BusinessIdKey)是否包含这个键,其它线程绕过,去执行getnext方法了
解决办法:
每个都需要判断,加锁,防止其它线程跳过init,去执行getnext方法
/// <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);
}
}
}
}