大家来讨论关于Hashtable线程安全问题

问题描述

需求:在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.

时间: 2024-09-27 12:31:25

大家来讨论关于Hashtable线程安全问题的相关文章

在多线程中使用静态方法是否有线程安全问题

   类的成员分为两类,静态成员(static member)和实例成员(instance member).静态成员属于类,实例成员则属于对象,即类的实例.     简单讨论一下在一个类中使用静态字段(static field)和静态方法(static method)是否会有线程安全问题.      我们在知道, 静态字段(static field)和静态方法(static method)的调用是通过类来调用.静态方法不对特定的实例操作,只能访问静态成员.实例方法可对特定的实例操作,既能访问静态

单例模式与线程安全问题浅析

           最近看到到Struts1与Struts2的比较,说Struts1的控制器是单例的,线程不安全的:Struts2的多例的,不存在线程不安全的问题.之后又想到了之前自己用过的HttpHandler...这些类,好像单例的线程安全问题确实是随处可见的.但是只是知道这个是不安全的,也没有认真分析过.接下来就仔细分析下. 一,修改单例模式代码       首先我先写一段单例类的代码:          /** * @ClassName: Sigleton * @Description

Spring-利用ThreadLocal解决线程安全问题

ThreadLocal是什么 ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象.当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本.所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本.从线程的角度看,这个变量就像是线程的本地变量,这也是类名中"Local"所要表达的意思. ThreadLocal的方法很简单,主要的就是4个方法 1234567891011 // 设

Servlet和JSP的线程安全问题

js|servlet|安全|问题 编写Servlet和JSP的时候,线程安全问题很容易被忽略,如果忽视了这个问题,你的程序就存在潜在的隐患. 1.Servlet的生命周期 Servlet的生命周期是由Web容器负责的,当客户端第一次请求Servlet时,容器负责初始化Servlet,也就是实例化这个Servlet类.以后这个实例就负责客户端的请求,一般不会再实例化其他Servlet类,也就是有多个线程在使用这个实例.Servlet之所以比CGI效率高就是因为Servlet是多线程的.如果该Ser

UNIX编程中错误输出的线程安全问题

系统调用失败原因分析 在 UNIX 编程中,我们会经常使用系统调用来完成期望的功能:而与此同时,我们也需要付出大段 的代码来检测.输出错误和其他意外情况. 以下是系统调用失败的可能原因: 系统可能出现资源短缺或者程序使用的资源可能超过系统为单个程序规定的上限.常见的情况有: 程序可能尝试分配大量内存,或者同时打开很多文件等. 程序执行操作时,可能会由于权限不足而被系统阻止.例如,程序可能会试图写一个只读的文件, 或者企图访问其他进程的内存空间. 传入系统调用的参数可能无效,原因可能是用户提供无效

HttpSession的线程安全问题及注意事项

HttpSession session = request.getSession(); List<Product> list = session.getAttribute("productCart"); myService.save(list); // 保存购物车数据到数据库 这个对象会被多次使用,也会被同一个用户的多个页面使用,所以他对于系统来说是线程不安全的. 比如用户在从产品列表里面选择产品,这面选择3种,他点了查看购物车 该用户还开了另一个页面,继续选择产品. 此时

java-Java 单例线程安全问题

问题描述 Java 单例线程安全问题 public class A { public final static A INSTANCE = new A(); private A(){} }这个类是不是线程安全的呢?单例能这样写吗? 解决方案 这种写法是线程安全的.但是在该类一开始被加载的时候INSTANCE = new A()就会被执行.具体参考陈皓的博客深入浅出单实例Singleton设计模式介绍得非常详细. 解决方案二: 如果单线程中初始化,多线程中应用就没问题. 解决方案三: 深入解析单例线

java初学者求教:关于线程安全问题

问题描述 java初学者求教:关于线程安全问题 List list = new ArrayList(); list = Collections.synchronizedList(list); 这时list是线程安全的,那么当我用增强for循环遍历list, 并且使用list的remove方法时,是不是就不安全了? 另外,如果list集合中存放的是一些带有图片的对象,比如dog public void paintTest(Graphisc g){ for(dog d:list){ g.drawIm

多线程-高手请进!!!---线程安全问题,怎么解决new String 问题

问题描述 高手请进!!!---线程安全问题,怎么解决new String 问题 场景是:一个订单号只能一个在付款,只能一个线程处理,不同的订单号支持并发处理 现在如果是new String("20140719140818");就有问题怎么解决 如果不是new出来的,什么情况会出现问题 public class Test { public static void main(String[] args) { new Thread(){ public void run(){ pay(&quo