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
Item item = (Item)hashtable[key];
item.SetName("foo");
database.Persist(item);
}
显然这个方法不是很好,就象你不应该为了修改DataSet中的一项而锁住整个DataSet.这样做效率太低.
于是就转而去锁要修改的项
2 lock(item) {
3 item.SetName("foo");
4 database.Persist(item);
5 }
但是这样的话 就不再线程安全了.从line 1到line 2这期间万一其他线程对HashTable做了修改或是删除了改Item怎么办, 你再将结果存入数据库时岂不是覆盖了新的Item值,甚至多出了原本被(别的线程)删除的Item.
比较好的解决方法:
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的时间也得到了缩短.效率上得到了提高.
希望这个例子对大家有没有帮助.