揭示同步块索引(上):从lock开始
转自:http://www.cnblogs.com/yuyijq/archive/2009/03/13/1410071.html
大家都知道引用类型对象除实例字段的开销外,还有两个字段的开销:类型指针和同步块索引(SyncBlockIndex)。同步块索引这个东西比起它的兄弟类型指针更少受人关注,显得有点冷落,其实此兄功力非凡,在CLR里可谓叱咤风云,很多功能都要借助它来实现。 接下来我会用三篇来介绍同步块索引在.NET中的所作所为。
既然本章副标题是从lock开始,那我就举几个lock的示例:
代码1
public class Singleton
{
private static object lockHelper = new object();
private static Singleton _instance = null;
public static Singleton Instance
{
get
{
lock(lockHelper)
{
if(_instance == null)
_instance = new Singleton();
}
return _instance;
}
}
}
代码2
1: public class Singleton
2: {
3: private static Singleton _instance = null;
4: public static Singleton Instance
5: {
6: get
7: {
8: object lockHelper = new object();
9: lock(lockHelper)
10: {
11: if(_instance==null)
12: _instance = new Singleton();
13: }
14: return _instance;
15: }
16: }
17: }
代码3
1: public class Singleton
2: {
3: private static Singleton _instance = null;
4: public static Singleton Instance
5: {
6: get
7: {
8: lock(typeof(Singleton))
9: {
10: if(_instance==null)
11: _instance = new Singleton();
12: }
13: return_instance;
14: }
15: }
16: }
代码4
1: public void DoSomething()
2: {
3: lock(this)
4: {
5: //dosomething
6: }
7: }
上面四种代码,对于加锁的方式来说(不讨论其他)哪一种是上上选?对于这个问题的答案留在本文最后解答。
让我们先来看看在Win32的时代,我们如何做到CLR中的lock的效果。在Win32时,Windows为我们提供了一个CRITICAL_SECTION结构,看看上面的单件模式,如果使用CRITICAL_SECTION的方式如何实现?
1: class Singleton
2: {
3: private:
4: CRITICAL_SECTIONg_cs;
5: static Singleton _instance = NULL;
6: public:
7: Singleton()
8: {
9: InitializeCriticalSection(&g_cs);
10: }
11: static Singleton GetInstance()
12: {
13: EnterCriticalSection(&g_cs);
14: if(_instance!=NULL)
15: _instance=newSingleton();
16: LeaveCriticalSection(&g_cs);
17: return_instance;
18: }
19: ~Singleton()
20: {
21: DeleteCriticalSection(&g_cs);
22: }
23: }
Windows提供四个方法来操作这个CRITICAL_SECTION,在构造函数里我们使用InitializeCriticalSection这个方法初始化这个结构,它知道如何初始化CRITICAL_SECTION结构的成员,当我们要进入一个临界区访问共享资源时,我们使用EnterCriticalSection方法,该方法首先会检查CRITICAL_SECTION的成员,检查是否已经有线程进入了临界区,如果有,则线程会等待,否则会设置CRITICAL_SECTION的成员,标识出本线程进入了临界区。当临界区操作结束后,我们使用LeaveCriticalSection方法标识线程离开临界区。在Singleton类的析构函数里,我们使用DeleteCriticalSection方法销毁这个结构。整个过程就是如此。
我们可以在WinBase.h里找到CRITICAL_SECTION的定义:
typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;
可以看到,CRITICAL_SECTION实际上就是RTL_CRITICAL_SECTION,而RTL_CRITICAL_SECTION又是在WinNT.h里定义的:
1: typedef struct _RTL_CRITICAL_SECTION{
2: PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
3: //
4: //Thefollowingthreefieldscontrolenteringandexitingthecritical
5: //sectionfortheresource
6: //
7: LONG LockCount;
8: LONG RecursionCount;
9: HANDLE OwningThread;//fromthethread'sClientId->UniqueThread
10: HANDLE LockSemaphore;
11: ULONG _PTRSpinCount;//forcesizeon64-bitsystemswhenpacked
12: }RTL_CRITICAL_SECTION,*PRTL_CRITICAL_SECTION;
从上面的定义和注释,聪明的你肯定知道Windows API提供的这几个方法是如何操作CRITICAL_SECTION结构的吧。在这里我们只需要关注OwningThread成员,当有线程进入临界区的时候,这个成员就会指向当前线程的句柄。
说了这么多,也许有人已经厌烦了,不是说好说lock么,怎么说半天Win32 API呢,实际上CLR的lock与Win32 API实现方式几乎是一样的。但CLR并没有提供CRITICAL_SECTION结构,不过CLR提供了同步块,CLR还提供了System.Threading.Monitor类。
实际上使用lock的方式,与下面的代码是等价的:
1: try{
2: Monitor.Enter(obj);
3: //…
4: }finally{
5: Monitor.Exit(obj);
6: }
(以下内容只限制在本文,为了简单,有的说法很片面,更详细的内容会在后面两篇里描述)
当CLR初始化的时候,CLR会初始化一个SyncBlock的数组,当一个线程到达Monitor.Enter方法时,该线程会检查该方法接受的参数的同步块索引,默认情况下对象的同步块索引是一个负数(实际上并不是负数,我这里只是为了叙说方便),那么表明该对象并没有一个关联的同步块,CLR就会在全局的SyncBlock数组里找到一个空闲的项,然后将数组的索引赋值给该对象的同步块索引,SyncBlock的内容和CRITICAL_SECTION的内容很相似,当Monitor.Enter执行时,它会设置SyncBlock里的内容,标识出已经有一个线程占用了,当另外一个线程进入时,它就会检查SyncBlock的内容,发现已经有一个线程占用了,该线程就会等待,当Monitor.Exit执行时,占用的线程就会释放SyncBlock,其他的线程可以进入操作了。
好了,有了上面的解释,我们现在可以判断本文前面给出的几个代码,哪一个是上上选呢?
对于代码2,锁定的对象是作为一个局部变量,每个线程进入的时候,锁定的对象都会不一样,它的SyncBlock每一次都是重新分配的,这个根本谈不上什么锁定不锁定。
对于代码3,一般说来应该没有什么事情,但这个操作却是很危险的,typeof(Singleton)得到的是Singleton的Type对象,所有Singleton实例的Type都是同一个,Type对象也是一个对象,它也有自己的SyncBlock,Singleton的Type对象的SyncBlock在程序中只会有一份,为什么说这种做法是危险的呢?如果在该程序中,其他毫不相干的地方我们也使用了lock(typeof(Singleton)),虽然它和这里的锁定毫无关系,但是只要一个地方锁定了,各个地方的线程都会在等待。
对于代码4,实际上代码4的性质和代码3差不多,如果有一个地方使用了DoSomething方法所在类的实例进行lock,而且恰好如this是同一个实例,那么两个地方就会互斥了。
由此看来只有代码1是上上选,之所以是这样,是因为代码1将锁定的对象作为私有字段,只有这个对象内部可以访问,外部无法锁定。 上面只是从文字上叙说,也许你觉得证据不足,我们就搬来代码作证。 使用ILDasm反编译上面单件模式的Instance属性的代码,其中一段IL代码如下所示:
1: IL_0007:stloc.1
2: IL_0008:call void [mscorlib]System.Threading.Monitor::Enter(object)
3: IL_000d:nop
4: .try
5: {
6: IL_000e:nop
7: IL_000f:ldsfld class Singleton Singleton::_instance
8: //….
9: //…
10: }
11: finally
12: {
13: IL_002b:ldloc.1
14: IL_002c:call void [mscorlib]System.Threading.Monitor::Exit(object)
15: IL_0031:nop
16: IL_0032:endfinally
17: }
为了简单,我省去了一部分代码。但是很明显,我们看到了System.Threading.Monitor.Enter和Exit。然后我们拿出Reflector看看这个Monitor到底是何方神圣。哎呀,发现Monitor.Enter和Monitor.Exit的代码如下所示:
1: [MethodImpl(MethodImplOptions.InternalCall)]
2: public static extern void Enter(objectobj);
3: [MethodImpl(MethodImplOptions.InternalCall),ReliabilityContract(Consistency.WillNotCorruptState,Cer.Success)]
4: public static extern void Exit(objectobj);
只见方法使用了extern关键字,方法上面还标有[MethodImpl(MethodImplOptions.InternalCall)]这样的特性,实际上这说明Enter和Exit的代码是在内部C++的代码实现的。只好拿出Rotor的代码求助了,对于所有"内部实现"的代码,我们可以在sscli20\clr\src\vm\ecall.cpp里找到映射:
1: FCFuncStart(gMonitorFuncs)
2: FCFuncElement("Enter", JIT_MonEnter)
3: FCFuncElement("Exit", JIT_MonExit)
4: …
5: FCFuncEnd()
原来Enter映射到JIT_MonEnter,一步步的找过去,我们最终到了这里:
Sscli20\clr\src\vm\jithelpers.cpp:
1: HCIMPL_MONHELPER(JIT_MonEnterWorker_Portable, Object* obj)
2: {
3: //省略大部分代码
4: OBJECTREF objRef = ObjectToOBJECTREF(obj);
5: objRef->EnterObjMonitor();
6: }
7: HCIMPLEND
objRef就是object的引用,EnterObjMonitor方法的代码如下:
1: void EnterObjMonitor()
2: {
3: GetHeader()->EnterObjMonitor();
4: }
GetHeader()方法获取对象头ObjHeader,在ObjHeader里有对EnterObjMonitor()方法的定义:
1: void ObjHeader::EnterObjMonitor()
2: {
3: GetSyncBlock()->EnterMonitor();
4: }
GetSyncBlock()方法会获取该对象对应的SyncBlock,在SyncBlock里有EnterMonitor方法的定义:
1: void EnterMonitor()
2: {
3: m_Monitor.Enter();
4: }
离核心越来越近了,m_Monitor是一个AwareLock类型的字段,看看AwareLock类内Enter方法的定义:
1: void AwareLock::Enter()
2: {
3: Thread* pCurThread = GetThread();
4: for (;;)
5: {
6: volatile LONG state = m_MonitorHeld;
7: if (state == 0)
8: {
9: // Common case: lock not held, no waiters. Attempt to acquire lock by
10: // switching lock bit.
11: if (FastInterlockCompareExchange((LONG*)&m_MonitorHeld, 1, 0) == 0)
12: {
13: break;
14: }
15: }
16: else
17: {
18: // It's possible to get here with waiters but no lock held, but in this
19: // case a signal is about to be fired which will wake up a waiter. So
20: // for fairness sake we should wait too.
21: // Check first for recursive lock attempts on the same thread.
22: if (m_HoldingThread == pCurThread)
23: {
24: goto Recursion;
25: }
26: // Attempt to increment this count of waiters then goto contention
27: // handling code.
28: if (FastInterlockCompareExchange((LONG*)&m_MonitorHeld, (state + 2), state) == state)
29: {
30: goto MustWait;
31: }
32: }
33: }
34: // We get here if we successfully acquired the mutex.
35: m_HoldingThread = pCurThread;
36: m_Recursion = 1;
37: pCurThread->IncLockCount();
38: return;
39: MustWait:
40: // Didn't manage to get the mutex, must wait.
41: EnterEpilog(pCurThread);
42: return;
43: Recursion:
44: // Got the mutex via recursive locking on the same thread.
45: m_Recursion++;
46: }
从上面的代码我们可以看到,先使用GetThread()获取当前的线程,然后取出m_MonitorHeld字段,如果现在没有线程进入临界区,则设置该字段的状态,然后将m_HoldingThread设置为当前线程,从这一点上来这与Win32的过程应该是一样的。如果从m_MonitorHeld字段看,有线程已经进入临界区则分两种情况:第一,是否已进入的线程如当前线程是同一个线程,如果是,则把m_Recursion递加,如果不是,则通过EnterEpilog(pCurThread)方法,当前线程进入线程等待队列。
通过上面的文字描述和代码的跟踪,在我们的大脑中应该有这样一张图了:

揭示同步块索引(上):从lock开始的更多相关文章
- 揭示同步块索引(中):如何获得对象的HashCode
转自:http://www.cnblogs.com/yuyijq/archive/2009/08/13/1545617.html 题外话:为了尝鲜,也兴冲冲的安装了Win7,不过兴奋之余却郁闷不已,由 ...
- [C#学习笔记]类型对象指针和同步块索引
写在前面 看<CLR via C#>第四章时,看到了类型对象指针和同步块索引这两个概念,不知如何解释,查看过相关资料之后,在此记录. 类型对象指针 <CLR via C#>中的 ...
- C# CLR via 对象内存中堆的存储【类型对象指针、同步块索引】
最近在看书,看到了对象在内存中的存储方式. 讲到了对象存储在内存堆中,分配的空间除了类型对象的成员所需的内存量,还有额外的成员(类型对象指针. 同步块索引 ),看到这个我就有点不懂了,不知道类型对象指 ...
- 十八、dbms_repair(用于检测,修复在表和索引上的损坏数据块)
1.概述 作用:用于检测,修复在表和索引上的损坏数据块. 2.包的组成 1).admin_tables语法:dbms_repair.admin_tables(table_name in varchar ...
- 和我一起打造个简单搜索之Logstash实时同步建立索引
用过 Solr 的朋友都知道,Solr 可以直接在配置文件中配置数据库连接从而完成索引的同步创建,但是 ElasticSearch 本身并不具备这样的功能,那如何建立索引呢?方法其实很多,可以使用 J ...
- 阿里面试题,为什么wait()方法要放在同步块中?
某天我在***的时候,突然有个小伙伴微信上说:“哥,阿里面试又又挂了,被问到为什么wait()方法要放在同步块中,没答出来!” 我顿时觉得**一紧,仔细回顾一下,如果wait()方法不在同步块中,代码 ...
- java多线程-同步块
Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java 同步块用来避免竞争.本文介绍以下内容: Java 同步关键字(synchronzied) 实例方法同步 ...
- synchronized同步块和volatile同步变量
Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而 ...
- Java同步块
原文:http://ifeve.com/synchronized-blocks/ Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java同步块用来避免竞争.本 ...
随机推荐
- Codeforces Round #241 (Div. 2) B. Art Union 基础dp
B. Art Union time limit per test 1 second memory limit per test 256 megabytes input standard input o ...
- 解决msi文件在XP上安装未完成
下载Ocra工具,然后删除"DIRCA_CheckFx"和"VSDCA_VsdLaunchConditions"这两个Action即可.第一步,下载并打开Ocr ...
- Android自定义view-CircleSeekbar
自定义view练手,效果图如下:实现功能 可设置圆环颜色和线宽及触摸后的颜色和线宽 可设置圆环内圈显示的文本内容及字体大小.颜色 可设置触摸点的图片 可设置触摸的有效范围 源码git ...
- 我的Android学习路线(二)
这两天的主要工作: 优化了一下布局界面,原本使用的是相对布局,直观省力,但是考虑了一下还是使用更加主流的线性布局. 完善了一下计算器的功能,比如加入小数运算. 使用线性布局的思路可以用下面的伪代码表示 ...
- 《Think in Java》(十一)持有对象
Java 中的持有对象就是容器啦,看完这一章粗略的了解了 Java 中的容器框架以及常用实现!但是容器框架中的接口以及实现类有好多,下午还得好好看看第 17 章--容器深入研究以及 Java 官方的文 ...
- 初学Selenium遇到的那些坑
一.遇到一个下拉选择框,可以点击继续选择: 所以click两次就可以了: 二.国际话问题 bdId.selectByIndex(index);//index位下拉框内容的下标,从0开始, 数组形式[ ...
- C#区块链零基础入门,学习路线图 转
C#区块链零基础入门,学习路线图 一.1分钟短视频<区块链100问>了解区块链基本概念 http://tech.sina.com.cn/zt_d/blockchain_100/ 二.C#区 ...
- 作业列表 of《软件测试技术》
作业1(截止时间3月22日) 请使用excel模板或word模板,完成对126邮箱登录功能的测试用例编写,界面如下图.提交到ftp. --------------------------------- ...
- POJ-3659-最小支配集裸题/树形dp
Cell Phone Network Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 7127 Accepted: 254 ...
- Ansible 小手册系列 九(Playbook)
playbook是由一个或多个"play"组成的列表.play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色.从根本上来讲所谓task无非 ...