直接上代码看现象来进行直观描述
示例场景描述:
redis中存储一个string类型数据,key=key,value=0,;
两个线程,每个线程循环100次,每次对key值进行+1操作;
期望结果:两个线程共执行了200次加操作,输出结果200;
这里为了展示锁的效果,我们故意不使用StringIncrement(其对数值已经实现了锁);
右键项目,添加StackExchange.redis
上图中是因为我已经安装过,所以右边显示的是卸载和更多,如果未安装过则显示的是安装按钮。
class Program
{
static ConnectionMultiplexer redis;
static IDatabase _db;
static RedisValue Token = Environment.MachineName;
static void Main(string[] args)
{
var options = ConfigurationOptions.Parse("localhost");
options.AllowAdmin = true;
redis = ConnectionMultiplexer.Connect(options);
_db = redis.GetDatabase();
//未使用锁 2个线程同时对一个数据进行加操作
for (var i = 0; i < 2; i++)
{
var key = "key";
Task.Factory.StartNew((index) =>
{
for (var j = 0; j < 100; j++)
{
var tmp = _db.StringGet(key);
var data = 0;
int.TryParse(tmp, out data);
_db.StringSet(key, (data + 1).ToString(), TimeSpan.FromSeconds(5));
}
Console.WriteLine("[未锁]-线程" + (Convert.ToInt32(index) + 1) + "值为:" + _db.StringGet(key));
}, i);
}
//使用锁
for (var i = 0; i < 2; i++)
{
Task.Factory.StartNew((index) =>
{
var key = "key2";
for (var j = 0; j < 100; j++)
{
while (true)
{
if (StringLockToUpdate(key))
break;
}
}
Console.WriteLine("[StackExchange锁]-线程" + (Convert.ToInt32(index) + 1) + "值为:" + _db.StringGet(key));
}, i);
}
//使用代码级锁
for (var i = 0; i < 2; i++)
{
var key = "key3";
Task.Factory.StartNew((index) =>
{
for (var k = 0; k < 100; k++)
{
StringLockToUpdateByNormalLock(key);
}
Console.WriteLine("[代码级锁]-线程" + (Convert.ToInt32(index) + 1) + "值为:" + _db.StringGet(key));
}, i);
}
Console.WriteLine("完成");
Console.ReadKey();
}
/// <summary>
/// StackExchange.redis锁
/// </summary>
/// <param name="key">数据Key</param>
/// <returns></returns>
static bool StringLockToUpdate(string key)
{
var flag = false;
//设置timespan避免死锁
if (_db.LockTake("LockKey", Token, TimeSpan.FromSeconds(5)))
{
try
{
var tmp = _db.StringGet(key);
var data = 0;
int.TryParse(tmp, out data);
_db.StringSet(key, data + 1, TimeSpan.FromSeconds(5));
flag = true;
}
catch (Exception)
{
//var a = ex.Message + "\r\n" + ex.StackTrace;
var b = string.Empty;
}
finally
{
_db.LockRelease("LockKey", Token);
}
}
return flag;
}
static object myLock = new object();
/// <summary>
/// 代码级锁实现锁
/// </summary>
/// <param name="key">数据key</param>
static void StringLockToUpdateByNormalLock(string key)
{
lock (myLock)
{
var tmp = _db.StringGet(key);
var data = 0;
int.TryParse(tmp, out data);
_db.StringSet(key, data + 1, TimeSpan.FromSeconds(5));
}
}
}
执行结果如图:
未使用锁的情况下,两个线程同时对同一个redis值进行变更,最终值无法按照我们预期保证数据准确。
代码级锁和StackExchange锁都实现了我们预期的效果;
根据业务需要,使用StackExchange锁或代码级锁均可实现锁效果。
上述效果中,StackExchange锁和代码级锁都实现了同样的效果
StackExchange是通过不断重试(While)来实现每一次每一次循环的操作都有效执行;
代码级锁中使用了Lock,他的实现是类似队列的;
所以从实现上述业务效果的性能上来看,代码级锁应该优于不断重试的方式;