最近看到这篇文章dotNetDR_的回复,让我想起一个真实发生的案例,下面就简单说说这个关于lock引用类型的一个不容易发现的隐藏缺陷。

某类库中的代码,封装了很简单的一个通用类,用于线程安全地执行某一种类型的特定方法,几行代码搞定:

    public class ConcurrentObjectExecutor<T> where T : IDisposable, new()
{
public void Start()
{
T obj = new T();
lock (obj)
{
Console.WriteLine(obj.ToString());
//do sth
}
}
}

设计这个类,估计本来主要是针对继承自特定接口的类型能够线程安全地执行某一个方法。如果泛型类型T为类(class),则程序运行没有任何问题,也能保证线程安全。

但是我们知道泛型约束只有继承自接口和new还远远不能保证T就是一个class,结构(struct)也可以继承接口,也可以new。比如自定义一个结构:

    struct OrderMessger : IDisposable
{
public void Dispose()
{
}
}

下面的代码可以编译通过,运行时也不会抛出异常(如果不看上下文,多数调用者估计就这么让它过去,很可能成为今后一个潜在的隐藏很深的bug):

    var executor = new ConcurrentObjectExecutor<OrderMessger>();
executor.Start();

很显然,上面的泛型程序看上去是lock了一个结构,也就是锁定了一个值类型。但是我们知道,lock关键字指定的锁定对象必须是引用类型。上面的示例中,实际情况是将结构实例obj隐式转换成了一个引用对象实例(也就是装箱,每次装箱都生成了一个新的实例),这样的lock是毫无意义的,因为在多线程的条件下,线程争用的obj已经不是指定的同一个实例(的引用)。

比较搞笑的是,直接写下面的代码,编译时直接在lock语句上报告有错误(编译时检测真是帮了大忙,ms为什么不把泛型检测搞的更智能些?):

           var obj = new OrderMessger();
lock (obj)
{
}

错误    1    “OrderMessger”不是 lock 语句要求的引用类型

解决的方法也很简单,泛型约束在原来的基础上再限定必须是class即可。

最后总结下:类库设计中,越是通用的东西考虑的应用场景越要周到,其中线程安全是非常重要必不可少的一个环节,线程安全实现过程中,如果对多线程同步原语理解不够深刻,很可能设计出有潜在缺陷的实现,MSDN关于Thread Safe的调用和说明值得类库开发者深刻学习和借鉴。

泛型实现中没有正确lock引用类型的一个隐藏bug分析的更多相关文章

  1. 【旧文章搬运】PsVoid中IrpCreateFile函数在Win7下蓝屏BUG分析及解决

    原文发表于百度空间,2010-04-05========================================================================== 这也许是我 ...

  2. cocos2d-x在android中响应返回键编译报错的bug分析

    先看一段代码如何在Android中加入返回按键的响应 <span style="font-size:18px;">自己派生CCKeypadDelegate的子类,然后注 ...

  3. 最近调试HEVC中码率控制, 发现HM里面一个重大bug

    最近调试HEVC中码率控制, 发现里面一个重大bug! 码率控制中有这么一个函数: Int TEncRCGOP::xEstGOPTargetBits( TEncRCSeq* encRCSeq, Int ...

  4. default 关键字泛型代码中的默认关键字(C# 编程指南)

    在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T: T 是引用类型还是值类型. 如果 T 为值类型,则它是数值还是结构. 给定参数化类型 T 的一个变量 t ...

  5. Unity 查找泛型List中的相同与不同数据

    Unity查找泛型集合中的不同数据 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- ...

  6. C#只能lock 引用类型的值 (转载)

    Lock:        C#只能lock 引用类型的值,如果lock一个int, bool,编译器会报错.    当一个互斥锁已被占用时,在同一线程中执行的代码仍可以获取和释放该锁.但是,在其他线程 ...

  7. Java 理论和实践: 了解泛型 识别和避免学习使用泛型过程中的陷阱

    Brian Goetz (brian@quiotix.com), 首席顾问, Quiotix 简介: JDK 5.0 中增加的泛型类型,是 Java 语言中类型安全的一次重要改进.但是,对于初次使用泛 ...

  8. Java并发指南4:Java中的锁 Lock和synchronized

    Java中的锁机制及Lock类 锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消 ...

  9. java中值类型和引用类型的区别

    [定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值,那么调用这个方法是传入的变量的值也将改变. 值类型表示复制一个当前变量传给方法,当你 ...

随机推荐

  1. js模块和级联

    1.模块 模块模式的一般形式是:一个定义了私有变量和函数的函数,利用闭包创建可以访问私有变量和函数的特权函数,最后返回这个特权函数,或者把它们保存到一个可访问的地方.使用模块模式就可以摒弃全局变量的使 ...

  2. 【Mail】搭建邮件服务器(LAMP+Postfix+Dovcot+PostfixAdmin+Roundcubemail)

    大纲 一.mail部署说明 二.安装准备 三.LMAP环境配置 四.配置postfixadmin 五.配置postfix 六.配置dovecot 七.测试SMTP和POP3服务 八.配置Roundcu ...

  3. MySQL提示符含义

    http://www.splaybow.com/post/mysql-prompt-introduce.html mysql> 准备好接受新命令. 说明:正常等待输入的提示符. -> 等待 ...

  4. using 语句中使用的类型必须可隐式转换为“System.IDisposable

    在使用 EF 出现 using 语句中使用的类型必须可隐式转换为“System.IDisposable 今天写在这里分享给大家 出现这样的问题,是因为没有引用  EntityFramework 这个程 ...

  5. 安装双系统window +ubuntu

    在网上,试过很多种方法,有U盘制作安装,感觉好复杂,这边有一个简便的方法就是使用Ubuntu 的 wubi安装. 一直想安装双Ubuntu 系统很久了,可是以前在大学时期的时候一直努力,好像都不行,这 ...

  6. easyUI的formatter使用

    <table class="easyui-datagrid" style="width:400px;height:250px" data-options= ...

  7. mysql mHA manager 状态修改

    启动:nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_fail ...

  8. 推荐一款跨平台的 Azure Storage Explorer

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  9. ArcGIS10.1 api for Flex开发用于ArcGIS 9.3时QueryTask 'Error #2032'错误的解决方案

    因客户GIS软件需求,将应用降级到低版本ArcGIS9.3,不仅数据有些样式.配色.字符有些问题,API也要相应“降级”,解决如下: 利用9.3+flex做QueryTask时候出现了[RPC Fau ...

  10. (转)RHEL/CentOS 6.x使用EPEL6与remi的yum源安装MySQL 5.5.x

    PS:如果既想获得 RHEL 的高质量.高性能.高可靠性,又需要方便易用(关键是免费)的软件包更新功能,那么 Fedora Project 推出的 EPEL(Extra Packages for En ...