最近看到这篇文章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. jQuery.ajaxSetup() 函数详解

    该函数用于更改jQuery中AJAX请求的默认设置选项.之后执行的所有AJAX请求,如果对应的选项参数没有设置, 将使用更改后的默认设置. //设置AJAX的全局默认选项$.ajaxSetup( {  ...

  2. easyui自定义标签 datagrid edit combobox 手动输入保存不上问题解决办法

    使用onEndEdit事件(该事件可以获取到editor对象,onAfterEdit事件获取不到Editor对象) 通过editor拿到输入数据并保存. int ci = 0; for(Column ...

  3. Process类

    public bool Process.Start () 启动(或重用)此 Process 组件的 StartInfo 属性指定的进程资源,并将其与该组件关联. public bool Process ...

  4. 最小生成树算法——Kruskal算法

    #include<stdio.h> #include<algorithm> #include<windows.h> using namespace std; str ...

  5. 解决Canvas.toDataURL 图片跨域问题

    如题,在将页面的图片地址进行本地输出时(Html2Canvas.js),因不同源存在跨域问题,会出现toDataURL访问权限问题: [Redirect at origin 'http://sub1. ...

  6. backbone框架思路

    Backbone是MVC框架.如何使用这个框架来简化前端过程呢?作为一个初学者,虽然网上有它的官方文档,但是都是讲的一些方法,思路分析的却对初学者来说,看得晕乎乎的.现在就把自己对这个框架的理解记录在 ...

  7. localStorage, localforage, web sql三者的比较

    最近的项目中用到了前端存储,最初选用的是localStorage,这个是html5里面新增的API,用法很简单.setItem getItem clear. 值得注意的是,localStorage中存 ...

  8. Centos7安装完毕后重启提示Initial setup of CentOS Linux 7 (core)的解决方法

    问题: CentOS7安装完毕,重新开机启动后显示: Initial setup of CentOS Linux 7 (core) 1) [x] Creat user 2) [!] License i ...

  9. Mini projects #5 ---- Memory

    课程全名:An Introduction to Interactive Programming in Python,来自 Rice University 授课教授:Joe Warren, Scott ...

  10. tail queue代码阅读

    tail queue是bdb中用的最多的数据结构. 定义在 src/dbinc/queue.h: 注: TRACEBUF,QMD_TRACE_HEAD等是为了 queue代码的debug, 这里移除出 ...