.NET的Monitor的锁定对象的细节是:
0 在运行程序时,CLR都会为同步锁而创建一个锁链表。
1 创建一个对象的时候,会在实例上分配4B的内存作为同步对象锁的一个指针syn,初始化的时候都为负值
2 当有锁定的对象的时候,CLR自动创建一个对象,然后添加到锁链表中,然后返回地址给syn,即syn指向了
链表中的某个对象,这个时候表示该对象被锁定
3 当解除锁定该对象的时候,删除该对象的syn所指向的对象,然后syn的值赋值为一个负值表示没有锁定。
关于Enter(),pause(),pauseall(),wait()方法的解释:
学过操作系统都知道,线程有三态模型:运行态,就绪态,等待态。
当一个线程创建完毕后都处于就绪态,等待CPU的调度,OS根据某种算法(Windows是抢占式)来调度就绪态的线程。
如果因为多个线程公用一个对象而要使用锁来同步这几个线程,例如线程A,B,C都要使用obj这个对象,必然都要用lock
之类的方式锁定该对象然后来使用。如果A这个时候锁定了obj,A就在运行态,这是A时间片到了,进就绪态,这是假设
该B线程去执行也要锁定obj,但是发现无法加锁,就进入了等待态(仔细阅读MSDN),假设C也试图进入发现对象已经
被锁定,只好进入了就绪态(但是要等待锁的使用权),然后CPU调度到A, 这是如果A中调用了wait().就会使A进入了
等待态(在Monitor中,唯一使一个线程进入到等待态的只有wait方法,使线程从等待态度进入就绪态的方法只有pause
和pausealls)然后释放锁,这是CPU就根据调度算法调用处于就绪态的线程然后再测试是否能加上锁。
Monitor比较用不好很容易死锁,有几个关键点要理解:
1 如果一个线程在执行时要申请一个对象的锁,而这时这个锁被其他线程占有,则该线程是不会进入等待队列的,
它会进入就绪队列去等待这个锁。
2 在调用exit(obj)的时候会释放占有的锁,但是不会通知在等待队列中的线程进入就绪队列中的,如果这时候就绪队列中
不包含该对象的线程,并且不去调用pause去主动通知,那么肯定会死锁的,这一点比较难以理解,多写点代码去调试下。
3 除非调用wait(),否则一个线程在等待锁的时候是不会进入等待队列中的,因为他如果进入了等待队列,在占有锁的线程在
exit()的时候是不会通知它进入就绪队列的。
总之:
wait():使处于运行态的线程释放锁,然后进入等待态。
pause:使处于等待态的线程进入就绪态
线程启动后立即进入就绪态,线程因其等待锁时会处于等待态度。
MSDN:
前拥有指定对象上的锁的线程调用wait()以释放该对象,以便其他线程可以访问它。调用方在等待重新获取锁期间被阻止,即进入了等待态。当调用方需要等待另一个线程操作导致的状态更改时,将调用此方法。
当线程调用 Wait 时,它释放对象的锁并进入对象的等待队列(等待态)。对象的就绪队列中的下一个线程(如果有)获取锁并拥有对对象的独占使用(如果没有就很危险了,因为只能通过pause()使之进入就绪态,这时关于这个对象的线程没有处于就绪态的就很危险了,可能死锁了)。所有调用 Wait 的线程都将留在等待队列中,直到它们接收到由锁的所有者发送的 Pulse 或 PulseAll 的信号为止。如果发送了 Pulse,则只影响位于等待队列最前面的线程。如果发送了 PulseAll,则将影响正等待该对象的所有线程。接收到信号后,一个或多个线程将离开等待队列而进入就绪队列。就绪队列中的线程被允许重新获取锁。
当调用线程重新获取对象上的锁后,此方法将返回。请注意,如果锁的持有者不调用 Pulse 或 PulseAll,则此方法将无限期地阻止。
调用方执行一次 Wait,与已为指定对象调用 Enter 的次数无关。概念上,Wait 方法存储调用方对对象调用 Enter 的次数,并按完全释放锁定对象所需要的次数调用 Exit。然后调用方在等待重新获取对象期间被阻止。当调用方重新获取锁时,系统按还原调用方的已保存 Enter 计数所需要的次数调用 Enter。调用 Wait 仅释放指定对象的锁;如果调用方是其他对象的锁的所有者,则不释放这些锁。
请注意,同步的对象包含若干引用,其中包括对当前拥有锁的线程的引用、对就绪队列(包含准备获取锁的线程)(注:红色部分是MSDN上原话,可见,如果线程在等待锁时回进入就绪态,即便CPU选中他,但是在通过系统总线获取数据时发现数据被锁定,还是运行不了的。)的引用和对等待队列(包含等待对象状态更改通知的线程)的引用。
Pulse、 PulseAll 和 Wait 方法必须从同步的代码块内调用。