问题描述
需求:在web应用程序,需要根据类名,将类的所有方法信息缓存。例子:Hashtable_table=Hashtable.Synchroized(newHashtable())hashtable的key为类名,value为Dictionary<string,MethodInfo>Dictionary的key为方法名称,value为方法信息。那么在读的时候,会先从hashtable读;不存在则反射获取MethodInfo,然后写入hashtable。问题:由于hashtable不是只读的,程序会在运行时读取和添加。也就是说,hashtable会被多个线程读和写!msdn对于Hashtable的解释是,如果没有任何线程在读取,则多个线程写是线程安全的;而如果有一个或多个线程在读并且有一个或多个线程在写,则不提供线程安全的访问。也就是说,按照上面的做法,可能会出现线程安全问题。请问大家对这类问题是如何设计和解决的。
解决方案
解决方案二:
引用楼主skyandcode的回复:
...先从hashtable读;不存在则反射获取MethodInfo,然后写入hashtable。...
这种情况Hashtable.Synchroized并不能帮助你。Hashtable.Synchroized只能保证单个的写操作是安全的,对分开的两次操作不能提供安全。
解决方案三:
如果满足以下假设:1、_table只添加,不删除。2、两次‘反射获取MethodInfo’只浪费时间,没有其他不良副作用。那么,你也可以用乐观的同步方法:Hashtable_table=Hashtable.Synchronized(newHashtable());stringkey="";if(!_table.ContainsKey(key)){objectmethodInfo=SlowGetMI();//可能有多个线程发现key不存在,同时试图‘反射获取MethodInfo’_table[key]=methodInfo;//单个写是安全,最后一个写入的线程胜出。}
解决方案四:
引用2楼Forty2的回复:
如果满足以下假设:1、_table只添加,不删除。2、两次‘反射获取MethodInfo’只浪费时间,没有其他不良副作用。那么,你也可以用乐观的同步方法:Hashtable_table=Hashtable.Synchronized(newHashtable());stringkey="";if(!_table.ContainsKey(key)){objectmethodInfo=SlowGetMI();//可能有多个线程发现key不存在,同时试图‘反射获取MethodInfo’_table[key]=methodInfo;//单个写是安全,最后一个写入的线程胜出。}
这样不是会出现:线程1,2同时进入if,线程1线程执行了_table[key]=methodInfo;线程2再执行此句时,抛异常了。
解决方案五:
引用3楼skyandcode的回复:
这样不是会出现:线程1,2同时进入if,线程1线程执行了_table[key]=methodInfo;线程2再执行此句时,抛异常了。
_table[key]=methodInfo;这样写没有问题,相当于线程1添加,线程2改写。_table.Add(key,methodInfo);这样写就有问题,Add是添加,因为有重值,线程2会抛出异常。
解决方案六:
引用4楼Forty2的回复:
Quote: 引用3楼skyandcode的回复:
这样不是会出现:线程1,2同时进入if,线程1线程执行了_table[key]=methodInfo;线程2再执行此句时,抛异常了。_table[key]=methodInfo;这样写没有问题,相当于线程1添加,线程2改写。_table.Add(key,methodInfo);这样写就有问题,Add是添加,因为有重值,线程2会抛出异常。
soga。你说的【乐观】是什么意思?按照这样写法,还是会出现一个线程在读,另一个线程在写的情况。
解决方案七:
多线程共用任何共享资源,都存在线程安全问题,我不是很理解你问的意思,如果单是线程安全问题,为什么不用互斥保证某一时间只有一条线程访问该资源
解决方案八:
引用4楼Forty2的回复:
Quote: 引用3楼skyandcode的回复:
这样不是会出现:线程1,2同时进入if,线程1线程执行了_table[key]=methodInfo;线程2再执行此句时,抛异常了。_table[key]=methodInfo;这样写没有问题,相当于线程1添加,线程2改写。_table.Add(key,methodInfo);这样写就有问题,Add是添加,因为有重值,线程2会抛出异常。
如果是add,只能自己加锁了stringkey="";lock(table.SyncRoot){if(!_table.ContainsKey(key)){objectmethodInfo=SlowGetMI();//可能有多个线程发现key不存在,同时试图‘反射获取MethodInfo’_table[key]=methodInfo;//单个写是安全,最后一个写入的线程胜出。}}不过这样一来,你完全用不着Synchronized,自己用lock去同步读写,悲观锁
解决方案九:
实际上,如果追求效率,是不应该用_table.ContainsKey+_table[key]来取值这样会查找两次,虽然它很快,但也比不上用TryGetValue
解决方案十:
这个乐观是指,放弃原子性,来提高效率。lock(lockObj){if(!_table.ContainsKey(key)){table.Add(key,SlowGetMI(key));}}这样做可以保证原子性,即保证判断后写入一定不会被其他线程干扰。但是,下锁本身有开销。如果不用保证原子性,比如3、4楼讨论的最后写入者胜出,结果又没有逻辑错误,就是一种’乐观‘的做法。它期待‘同时反射获取MethodInfo’可以发生,但不会常见。允许偶尔‘同时反射获取MethodInfo’,可能比每次‘下锁’的方式效率更高。
解决方案十一:
引用9楼Forty2的回复:
这个乐观是指,放弃原子性,来提高效率。lock(lockObj){if(!_table.ContainsKey(key)){table.Add(key,SlowGetMI(key));}}这样做可以保证原子性,即保证判断后写入一定不会被其他线程干扰。但是,下锁本身有开销。如果不用保证原子性,比如3、4楼讨论的最后写入者胜出,结果又没有逻辑错误,就是一种’乐观‘的做法。它期待‘同时反射获取MethodInfo’可以发生,但不会常见。允许偶尔‘同时反射获取MethodInfo’,可能比每次‘下锁’的方式效率更高。
嗯,我理解你的意思。按照上面的写法进行“写操作”,那如果其他地方还有“读操作呢”?这个时候是不是只能用【读写者】来解决了?
解决方案十二:
引用7楼dongxinxi的回复:
Quote: 引用4楼Forty2的回复:
Quote: 引用3楼skyandcode的回复:
这样不是会出现:线程1,2同时进入if,线程1线程执行了_table[key]=methodInfo;线程2再执行此句时,抛异常了。_table[key]=methodInfo;这样写没有问题,相当于线程1添加,线程2改写。_table.Add(key,methodInfo);这样写就有问题,Add是添加,因为有重值,线程2会抛出异常。
如果是add,只能自己加锁了stringkey="";lock(table.SyncRoot){if(!_table.ContainsKey(key)){objectmethodInfo=SlowGetMI();//可能有多个线程发现key不存在,同时试图‘反射获取MethodInfo’_table[key]=methodInfo;//单个写是安全,最后一个写入的线程胜出。}}不过这样一来,你完全用不着Synchronized,自己用lock去同步读写,悲观锁
这个我知道。如果其它有读操作,是不是依然会有问题。
解决方案十三:
为什么不用ConcurrentDictionary
解决方案十四:
引用12楼github_22161131的回复:
为什么不用ConcurrentDictionary
一样,多线程读写同一个集合肯定是有问题的。要解决这个问题,只能用ReaderWriter了。
解决方案十五:
引用13楼skyandcode的回复:
Quote: 引用12楼github_22161131的回复:
为什么不用ConcurrentDictionary一样,多线程读写同一个集合肯定是有问题的。要解决这个问题,只能用ReaderWriter了。
怎么可能一样,Hashtable非线程安全,ConcurrentDictionary线程安全,而且提供GetOrAdd,AddOrUpdate这些线程安全的复合操作。像你的需求用GetOrAdd就能实现了。
解决方案:
应该用ReaderWriterLock,写的时候保证只有一个线程读的时候可以多线程典型的读者写着问题
解决方案:
在这里ReaderWriterLock/ReaderWriterLockSlim是最不应该用的,还不如lock,有耐心的就看完,没耐心的看里面的Conclusions
解决方案:
来自MSDN2.0zh-cnHashtable是线程安全的,可由多个读取器线程或一个写入线程使用。多线程使用时,如果任何一个线程执行写入(更新)操作,它都不是线程安全的。若要支持多个编写器,如果没有任何线程在读取Hashtable对象,则对Hashtable的所有操作都必须通过Synchronized方法返回的包装完成。4.0zh-cnHashtable是线程安全的,可由多个读取器线程和一个写入线程使用。多线程使用时,如果只有一个线程执行写入(更新)操作,则它是线程安全的,从而允许进行无锁定的读取(若编写器序列化为Hashtable)。若要支持多个编写器,如果没有任何线程在读取Hashtable对象,则对Hashtable的所有操作都必须通过Synchronized方法返回的包装完成。2.0en-usHashtableisthreadsafeforusebymultiplereaderthreadsandasinglewritingthread.Itisthreadsafeformulti-threadusewhenonlyoneofthethreadsperformwrite(update)operations,whichallowsforlock-freereadsprovidedthatthewritersareserializedtotheHashtable.TosupportmultiplewritersalloperationsontheHashtablemustbedonethroughthewrapperreturnedbytheSynchronizedmethod,providedthattherearenothreadsreadingtheHashtableobject.