对象引用的正确性在多线程环境下是一个复杂的问题,请参考,处理由引用计数引起的泄漏.简单来说,我们应该尽量减少使用强引用,否则将有可能产生[处理由引用计数引起的泄漏]一文中描述的难以察觉的内存泄漏问题.也就是说,大多数情况下我们应该使用一个弱引用来指代一个对象,当我们真正需要访问这个对象的时候才将其转换成实际的对象.所以可以弱引用理解为一种handle,它只是底层对象表示的一层间接引用.

可以考虑如下场景:

我们设计了一种网络库,分为IO层和逻辑层,IO层管理实际的socket对象,而逻辑层能看到的只是socket的一个handle.逻辑层需要发送数据的时候,将handle和数据一起打包交给IO层,IO层把handle转化成实际的socket对象并完成数据发送.那么问题来了,如果在IO层收到一个发送请求时,那个handle对应的socket实际上已经销毁,那么对handle的转换就应该反映出这种情况,让转换返回一个空指针.

因为在[处理由引用计数引起的泄漏]描述的算法中,refobj *cast2refobj(ident _ident);atomic_32_t refobj_dec(refobj *r);两个方法是至关重要,并且实现相对复杂,所以本文主要目的就是介绍这两个函数的作用及其正确性.

我们首先来看下refobj_dex:

atomic_32_t refobj_dec(refobj *r)
{
atomic_32_t count;
int c;
struct timespec ts;
assert(r->refcount > 0);
if((count = ATOMIC_DECREASE(&r->refcount)) == 0){
r->identity = 0;
c = 0;
for(;;){
if(COMPARE_AND_SWAP(&r->flag,0,1))
break;
if(c < 4000){
++c;
__asm__("pause");
}else{
ts.tv_sec = 0;
ts.tv_nsec = 500000;
nanosleep(&ts, NULL);
}
}
r->destructor(r);
}
return count;
}

关键部分在引用计数为0,要准备销毁对象的分支.首先将对象的identity置0,然后在一个for循环中对尝试flag变量置1,只有当设置成功才会退出循环执行最后的析构函数.这里的主要迷惑之一是for循环和flag变量的作用是什么.让我们先看下cast2refobj的实现在回来讨论;

refobj *cast2refobj(ident _ident)
{
refobj *ptr = NULL;
if(!_ident.ptr) return NULL;
TRY{
refobj *o = (refobj*)_ident.ptr;
do{
atomic_64_t identity = o->identity;
if(_ident.identity == identity){
if(COMPARE_AND_SWAP(&o->flag,0,1)){
identity = o->identity;
if(_ident.identity == identity){
if(refobj_inc(o) > 1)
ptr = o;
else
ATOMIC_DECREASE(&o->refcount);
}
o->flag = 0;
break;
}
}else
break;
}while(1);
}CATCH_ALL{
ptr = NULL;
}ENDTRY;
return ptr;
}

cast2refobj的作用就是将一个handle转换成对象,如果对象未被销毁返回对象,否则返回NULL.在do循环中,首先判断handle保存的identity与实际对象的是否一致,如果不一致表明handle中存放的对象肯定已经不是原来的对象了,所以返回NULL.而当identity一致的时候,首先做的第一件事又是对flag置1.可见这个flag是这个算法的重点.现在我们来讨论flag的作用.

flag主要由两个作用:

  1. 防止多个线程同时进入cast2refobj的核心部分,让我们考虑以下场景:

有A,B,C3个线程,A线程执行refobj_dec,在成功执行if((count = ATOMIC_DECREASE(&r->refcount)) == 0)之后,r->identity = 0;之前暂停.B,C则几乎同时执行cast2refobj,这个时候因为identity还未被清0,所以B,C看到的identity必然与其持有的handle的保持一致,如果没有if(COMPARE_AND_SWAP(&o->flag,0,1))这行代码我们看看会发生什么事情.假设B线程先执行, 它执行if(refobj_inc(o) > 1)的时候返回值应该是1,那么条件判断失败,没有将ptr设置为o,所以ptr依旧是NULL.但在执行完判断在准备执行另一个分支的ATOMIC_DECREASE(&o->refcount);之前它也被暂停,那么当C执行if(refobj_inc(o) > 1)它会进入ptr=o的分支(因为refobj_inc(o)会返回2),也就是说,转换成功,而实际上返回的却是一个正准备销毁的对象.flag就是为了防止这种情况的发生,它使得多个线程执行cast2refobj的时候,只能互斥的进入if(refobj_inc(o) > 1).

  1. r->destructor延后执行,使得执行cast2refobj并已经进入if(COMPARE_AND_SWAP(&o->flag,0,1))内部的线程先退出cast2refobj,然后再执行 r->destructor.

另外还要注意的是cast2refobj是被TRY CATCH所保护的,这样做的原因在于,在内存压力大的情况下,被销毁对象的内存可能立刻归还给系统,那么对对象的访问将会产生访问异常.我们必须捕获这个异常,同时让函数返回NULL(异常出现表明handle持有的对象必定是非法的了).

一种线程安全的handle的更多相关文章

  1. Java 四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

    介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new T ...

  2. Java四种线程池的使用

    Java通过Executors提供四种线程池,分别为:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.newFixe ...

  3. Java四种线程池

    Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor 时间:20 ...

  4. 三种线程不安全现象描述(escaped state以及hidden mutable state)

    hidden mutable state和escaped state是两种线程不安全问题:两者原因不同,前者主要是由于类成员变量中含有其他对象的引用,而这个引用是immutable的:后者是成员方法的 ...

  5. Java四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

    1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? Java new Thread(new Runnable() { @Override public void ru ...

  6. Java通过Executors提供四种线程池

    http://cuisuqiang.iteye.com/blog/2019372 Java通过Executors提供四种线程池,分别为:newCachedThreadPool创建一个可缓存线程池,如果 ...

  7. JAVA四种线程池实例

    1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗?   Java   1 2 3 4 5 6 7 new Thread(new Runnable() {        ...

  8. AsyncTask两种线程池

        AsyncTask两种线程池  http://bbs.51cto.com/thread-1114378-1.html (API 3.0以后): 1.THREAD_POOL_EXECUTOR, ...

  9. Java 四种线程池的用法分析

    1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { ...

随机推荐

  1. Xshell_Using X11 forwarding

    FROM:http://www.netsarang.com/tutorial/xshell/1018/Using_X11_forwarding The X11 forwarding feature i ...

  2. Zabbix3.0 自动电话报障

    第一种:Pagerduty 网站:www.pagerduty.com 优点:老牌服务商,稳定 缺点:贵,英文,网站要FQ 价格参考(34美元每月才25个电话,*29每月是包年才有的价格) 安装方式: ...

  3. JAVA基于缓冲的文件读写操作

    File f2 = new File("e://index.java"); BufferedReader reader = new BufferedReader(new Input ...

  4. 中文版Windows Server 2012 R2更改为英文显示语言

    1. 下载Windows Server 2012 R2多语言包(下载页面,直接下载链接). 2. 将win_svr_2012_r2_64bit_multi_language_lp_oem.img解压或 ...

  5. .net 架构师/经理招聘,长期有效

    岗位职责: 1.参与项目的需求分析和业务评审 2.负责项目的技术架构: 3.负责指导开发小组完成开发以及单元测试: 4.负责解决开发人员的技术问题,并对开发人员的代码进行Review. 任职要求: 1 ...

  6. 【译】UNIVERSAL IMAGE LOADER.PART 2---ImageLoaderConfiguration详解

    ImageLoader类中包含了所有操作.他是一个单例,为了获取它的一个单一实例,你需要调用getInstance()方法.在使用ImageLoader来显示图片之前,你需要初始化它的配置-Image ...

  7. [ACM_模拟] ZJUT 1155 爱乐大街的门牌号 (规律 长为n的含k个逆序数的最小字典序)

    Description ycc 喜欢古典音乐是一个 ZJUTACM 集训队中大家都知道的事情.为了更方便地聆听音乐,最近 ycc 特意把他的家搬到了爱乐大街(德语Philharmoniker-Stra ...

  8. 使用grunt打包ueditor源代码

    支持版本支持 UEditor 1.3.0+ 的版本 使用方法1.线上下载ueditor下载地址:ueditor,要下载"完整版 + 源码" 2.安装nodejs下载nodejs并安 ...

  9. Hibernate 的saveOrUpdate方法(转)

    hibernate提供了saveOrUpdate的方法来进行数据库的操作.hibernate会根据对象的状态决定是insert还是update,其根本是通过xml文件中unsaved-value来确定 ...

  10. Win10年度更新开发必备:VS2015 Update 3正式版下载汇总

    微软在06月27日发布了Visual Studio 2015 Update 3 .在MSDN中微软也提供下载,而且MSDN的Visual Studio 2015 Update 3与官方免费下载的文件是 ...