先展示一段代码出来。方法 Method1和Method2二方法的功能都相同:首先检查字典中是否有缓存的类的属性信息,如果没有则反射获取类的属性信息,放入缓存字典并返回;如果有,则返回缓存字典中的属性信息。
大家看下面的 Method1 和 Method2 二方法有什么区别?
private static Dictionary<Type, Dictionary<string, PropertyInfo>> _classProperties = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
// 方法 Method1
private static Dictionary<string, PropertyInfo> Method1(Type pType)
{
Dictionary<string, PropertyInfo> result;
if (!_classProperties.TryGetValue(pType, out result))
{
lock (_classProperties)
{
if (!_classProperties.TryGetValue(pType, out result))
{
result = new Dictionary<string, PropertyInfo>();
// 获取类的所有公开属性
PropertyInfo[] pis = pType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo pi in pis)
{
result.Add(pi.Name, pi);
}
_classProperties[pType] = result;
}
}
}
return result;
}
// 方法 Method2
private static Dictionary<string, PropertyInfo> Method2(Type pType)
{
Dictionary<string, PropertyInfo> result;
if (!_classProperties.TryGetValue(pType, out result))
{
lock (_classProperties)
{
if (!_classProperties.TryGetValue(pType, out result))
{
_classProperties[pType] = result = new Dictionary<string, PropertyInfo>();
// 获取类的所有公开属性
PropertyInfo[] pis = pType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo pi in pis)
{
result.Add(pi.Name, pi);
}
}
}
}
return result;
}
区别在于,我把 Method1中以红色字体标示的二行代码,在 Method2中用一行代码代替了。肯定有人会说,这不是很简单的代码吗!嗯,看起来是很简单。在大多情况下,二个方法谁都可以代替谁。然而,当在高并发的生产环境下,方法二却是有BUG的。原因何在?
方法一分析:当在同一时间(时段)有多个外部代码首次调用方法一时,由于缓存字典中没有属性信息,所有的调用点都阻塞在 lock(_classProperties) 处,而只有最先调用的代码进入 lock 内部执行,lock 内部执行一系列动作后,最后把获取到的类属性信息加入缓存字典中返回并释放锁(记住,是最后才加入缓存),释放锁后,其它等待的调用点就一窝锋的进入 lock 内部,由于此时缓存中已经有了类属性信息,在从缓存中获取后直接返回,不再反射。在第一个调用点锁定期间,如果有另外的调用点调用,也会等待第一个锁结束后继续。
方法二分析:与方法一的主要区别在于,如果第一个调用点在锁定期间,代码执行在_classProperties[pType] = result = new Dictionary<string, PropertyInfo>() 行的同时,又有第二个调用点调用方法二,则第二个调用点在缓存字典中能够得到类对应的内容,但是,此时第一个调用点还没有执行完,也就是说,字典中的属性信息是不完整的,那么第二个调用点的外部代码在往下执行时,有可能会出错。这就是方法二为什么会有BUG的原因之所在。