Application Lock 锁定技术

今天同事告诉我, 锁 hashtable 应该锁它的 SyncRoot 属性而不应该锁它的实例, 例如:

Hashtable ht = new Hashtable();
lock(ht.SyncRoot)
{
...
}

看了 .Net Framework 文档, 给的例子也是锁 SyncRoot 属性, 说如果锁实例的话不能保证在并发情况下的同步, 我很疑惑, 为什么不能锁 hashtable 实例本身呢?

做了个实验, 两个线程 A 和 B, 用锁实例和锁 SyncRoot 两种方式测试, 都没有问题, 结果是一样的。

后来, 用 Hashtable.Synchronized 创建自动线程同步的 hashtable, 终于明白了 SyncRoot 的作用。先说说自动线程同步的 Hashtable: 如果 Hashtable 要允许并发读但只能一个线程写, 要这么创建 Hashtable 实例:

Hashtable hashtable = Hashtable.Synchronized(new Hashtable());

这样, 如果有多个线程并发的企图写 hashtable 里面的 item, 则同一时刻只能有一个线程写, 其余阻塞; 对读的线程则不受影响。

测试的代码是这样的:

Hashtable _hashtable = Hashtable.Synchronized(new Hashtable());

public void TestLock()
{
Thread t1 = new Thread(new ThreadStart(SyncFunctionA));
Thread t2 = new Thread(new ThreadStart(SyncFunctionB));

t1.Start();
t2.Start();

Thread.Sleep(8000);

Console.WriteLine("hashtable[" + _key_a + "] = " + _hashtable[_key_a]);
}

private void SyncFunctionA()
{
lock (_hashtable.SyncRoot)
{
Thread.Sleep(5000);
_hashtable[_key_a] = "Value set by SyncFunctionA";
}
}

private void SyncFunctionB()
{
Console.WriteLine("hashtable[" + _key_a + "] = " + _hashtable[_key_a]);
_hashtable[_key_a] = "Value set by SyncFunctionB";

}

为了清楚的看到效果, 线程 A 用了锁, 并睡眠 5 秒, 睡醒后设置一下 hashtable 里的 item. 线程 B 先读一下 hashtable 里的 item, 再写 hashtable 里的 item。因为对 SyncRoot 加了锁, 即使线程 B 没有显式的对 hashtable 加锁, 但在 _hashtable[_key_a] = "Value set by SyncFunctionB" 一句上也会被 hashtable 自动锁住, 直到线程 A 释放掉 SyncRoot 锁为止。如果线程 A 不是锁 SyncRoot 而是锁 hashtable 实例本身, 那么线程 B 不会在 _hashtable[_key_a] = "Value set by SyncFunctionB" 上被自动锁住。

所以, 总结如下:

如果想锁整个 hashtable, 包括读和写, 即不允许并发的读和写, 那应该锁 hashtable 实例;
如果想允许并发的读, 不允许并发的写, 那应该创建 Synchronized 的 hashtable, 并对要加锁的一块代码用 SyncRoot 锁住, 如果不需要对一块代码加锁, 则 hashtable 会自动对单个写的操作加锁。


application.lock
'这里可以放一个计时器,记录从页面开始到执行到这里所用的时间
for i = 0 to 30000
 response.write(i&"<br/>")
 response.flush
next
application.unlock

application锁定时,lock和unlock之间的代码,将不能并发执行。以上代码,用2个页面同时执行,第二个页面需要等到第一个页面执行完之后才开始执行。

注:lock和unlock之间不一定要出现application对象的修改,如application("a")=1

关于Hashtable的:
线程安全
Hashtable 可以同时安全地支持一个编写器和多个阅读器。若要支持多个编写器,所有操作必须通过由 Synchronized 方法返回的包装完成。

枚举一个集合在本质上不是一个线程安全的过程。甚至在对集合进行同步处理时,其他线程仍可以修改该集合,这会导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。

使用Hashtable时,写加锁,读使用Key而不使用遍历多线程情况下从未出问题

我们的产品中很多地方用到Hashtable,而且都是多线程的,都没有什么问题;
System.Collection下的class都是支持单线程写多线程读的,所以只有一个线程写的话没有必要加锁;除非用了
IEnumerator,象lx--所说的;
但是如果需要用到IEnumerator时,为什么要用Hashtable呢,我觉得很奇怪;
我们也有类似的功能,一个线程把Item写入队列,一个线程读出来处理. 方法:1.MSMQ; 2.用ArrayList或Queue自己封装一个,在存取方法里面加锁;3.用两个Collection交替.替换的时候加锁就行了,效率很高;
我觉得这个完全应该使用MSMQ啊,用hashTable并不适合这种场景。去除共享问题不算,会导致系统当机的时候数据丢失,而且内存开销也是一个问题。
如果单条数据小于4M,建议还是采用MSMQ.耦合性也可以大大降低

近日有一些关于数据库并发控制的随笔.
我个人只是看过一些资料,并没有对数据库并发做过实验.所以也不能给出什么太好的解决方案.
不过这里有一个有关解决hashtable的线程安全的方法, 可能有一些参考价值.

有关HashTable的操作很简单,只是想实现读取HashTable中的一项,然后存入数据库中.只不过比往常不同的是要考虑线程的安全性问题.

最简单的解决办法就是在此期间锁住整个HashTable
lock(hashtable) {
  Item item 
= (Item)hashtable[key];
  item.SetName(
"foo");
  database.Persist(item);
}

显然这个方法不是很好,就象你不应该为了修改DataSet中的一项而锁住整个DataSet.这样做效率太低.

于是就转而去锁要修改的项
1    Item item = (Item)hashtable[key];
2    lock(item) {
3    item.SetName("foo");
4   database.Persist(item);
5     }

但是这样的话 就不再线程安全了.从line 1到line 2这期间万一其他线程对HashTable做了修改或是删除了改Item怎么办, 你再将结果存入数据库时岂不是覆盖了新的Item值,甚至多出了原本被(别的线程)删除的Item.

比较好的解决方法:
Item item = null;
bool lockWasSuccessful = false;

while(true{
    
lock(hashtable) {
        item 
= hashtable[key];
        lockWasSuccessful 
= Monitor.TryEntry(item);
    }

    
if(lockWasSuccessful == false{
        Thread.Sleep(
0);
        
continue;
    }

// If we reach here, the item was successfully locked
    try {
// Application code goes here
    }

    
finally {
        Monitor.Exit(item);
    }

    
break;
}



这里使用了lock和Monitor结合的方法.
先lock住整个HashTable, 不过时间很短.lock的目的仅仅是获得进入Item的权限.
如果此时没有其他线程在操作该Item,就立即释放lock进行操作.如果有其他线程在操作,Monitor.TryEntry(item)就会返回false.使得你无法进入该Item
也就保证了安全,同时lock整个HashTable的时间也得到了缩短.效率上得到了提高.

希望这个例子对大家有没有帮助.
 
posted @ 2008-05-24 18:49  winnerlan  阅读(2694)  评论(1编辑  收藏  举报