对Monitor的使用可以防止lock的时间过长并且可以设置其对应的超时时间达到对预期代码的一个控制,合理的使用timeout可以有助于程序的健壮性。但是对于不同的并发程序可能某些时候我们需要的粒度是不一样的,从而产生的一个问题是需要更细粒度的锁来保证,又因为默认的字符串无法共享导致的无法通过string来进行锁粒度的细分所以可能需要自己重写一个锁来保证到达更细粒度的控制,可能这时候有人要说不是有string.Intern可以进行字符串保留,达到类似的效果,对于这个我只想说内部确定的字符串确实可以达到这个效果,但是因为字符串保留机制就导致了gc不会对此进行回收,会导致如果外部输入的string是不可控的情况下就可以给程序造成诸如oom的问题,对此我抛砖引玉希望各位博友可以提出宝贵的意见或者建议,或者有现成的更好的解决方案也请分享一下,下面贴代码:
public class MonitorStr { private MonitorStr() { } private static ConcurrentDictionary<string, MonitorStrEntry> _lockDics = new ConcurrentDictionary<string, MonitorStrEntry>(); private const int _concurrentCount = 31; private static object[] _lockers = new object[_concurrentCount]; static MonitorStr() { for (int i = 0; i < _concurrentCount; i++) { _lockers[i] = new object(); } } private static int GetIndex(string key) { return Math.Abs(key.GetHashCode() % _concurrentCount); } public static bool TryEnter(string key, int timeoutMillis) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); MonitorStrEntry entry = null; var locker = _lockers[GetIndex(key)]; lock (locker) { if (!_lockDics.TryGetValue(key, out entry)) { entry = new MonitorStrEntry(); _lockDics[key] = entry; } entry.Increment(); } var acquired = Monitor.TryEnter(entry, timeoutMillis); if (!acquired) entry.Decrement(); return acquired; } public static void Exit(string key) { var entry = _lockDics[key]; Monitor.Exit(entry); if (entry.Decrement() == 0) { var locker = _lockers[GetIndex(key)]; lock (locker) { if (entry.CanRemove()) { Console.WriteLine(key + "remove"); _lockDics.TryRemove(key, out var v); } } } } class MonitorStrEntry { private int _lockCount; public int Increment() { Interlocked.Increment(ref _lockCount); return _lockCount; } public int Decrement() { Interlocked.Decrement(ref _lockCount); return _lockCount; } public bool CanRemove() { return _lockCount == 0; } } }
这个代码的原理就是利用数组对象锁和传入的string key进行对不同key之间的粒度的区分,因为不同的key之间的hashcode不一致所以对取到的锁对象locker也不一样,达到降低锁并发的级别,字典存储的entry内部维护一个锁的加锁次数达,利用cas保证并发多线程安全且高效。
如何使用
var key = "testKey"; var timeoutMillis = 3000; var acquired = false; try { acquired = MonitorStr.TryEnter(key, timeoutMillis); if (acquired) { //Do Something } else { throw new XXXTimeOutException(); } } finally { MonitorStr.Exit(key); }
哪个场景下可以使用呢?
//使用场景,诸如多线程查数据库环境的情况下 var userId = "xxxx"; var user = Cache.Query(userId); if (user == null) { var acquired = false; try { acquired = MonitorStr.TryEnter(key, timeoutMillis); if (acquired) { //Do Something user = Cache.Query(userId); if (user == null) { user = DB.Query(userId); } } else { throw new XXXTimeOutException(); } } finally { MonitorStr.Exit(key); } }
谢谢