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

    接上文 这里先写一个GET请求的 1.先在VIEW里定义一个方法 代码如下: @api_view(['GET', 'POST']) def book_request(request): if requ ...

  2. CoreCLR中超过3万行代码的gc.cpp文件的来源

    在CoreCLR的开源代码中,GC的主要实现代码gc.cpp文件大小竟然有1.17MB,打开文件一看,竟然有35490行!第一次见到如此多行的单个代码文件. github都不让直接查看:https:/ ...

  3. Hive的安装部署

    1.环境准备 1.1软件版本 hive-0.14 下载地址 2.配置 安装hive的前提,必需安装好hadoop环境,可以参考我之前Hadoop社区版搭建,先搭建好hadoop环境:接下来我们开始配置 ...

  4. Javascript实现计数器,定时警告和停止

    <html> <head> <meta charset="utf-8"> <title>定时警告</title> < ...

  5. web前端基础——补充

    1  布局和事件 布局效果如下(标题和内容都居中,两边留空白) 布局代码如下 <!DOCTYPE html> <html lang="en"> <he ...

  6. Avizo - 高级三维可视化及分析软件

    今天从材料科学应用角度介绍Avizo的基本功能. Avizo是一款先进的三维可视化及分析应用,可用来探索从断层扫描.显微镜.核磁共振成像及更多其他技术获得的材料科学数据.从简单的可视化与测量到高级的图 ...

  7. 【转】对 Xcode 菜单选项的详细探索(干货)

    http://www.cocoachina.com/ios/20151204/14480.html 本文调研Xcode的版本是 7.1,基本是探索了菜单的每一个按钮.虽然从xcode4一直用到了xco ...

  8. XMPP系列1:简介

    通俗解释其实XMPP 是一种很类似于http协议的一种数据传输协议,它的过程就如同“解包装--〉包装”的过程,用户只需要明白它接收的类型,并理解它返回的类型,就可以很好的利用xmpp来进行数据通讯.系 ...

  9. 微信、陌陌等著名IM软件设计架构详解

    对微信.陌陌等进行了分析,发出来分享一下(时间有些久了) 电量:对于移动设备最大的瓶颈就是电量了.因为用户不可能随时携带电源,充电宝.所以必须考虑到电量问题.那就要检查我们工程是不是有后台运行,心跳包 ...

  10. 扩展ViewFlow避免和ViewPager滑动冲突,同时支持无限循环,并完美和CircleFlowIndicator结合

    首先,为了避免滑动冲突,我们要继承ViewFlow,重写onInterceptTouchEvent public class MyViewFlow extends ViewFlow { private ...