對Monitor的使用能夠防止lock的時間過長而且能夠設置其對應的超時時間達到對預期代碼的一個控制,合理的使用timeout能夠有助於程序的健壯性。可是對於不一樣的併發程序可能某些時候咱們須要的粒度是不同的,從而產生的一個問題是須要更細粒度的鎖來保證,又由於默認的字符串沒法共享致使的沒法經過string來進行鎖粒度的細分因此可能須要本身重寫一個鎖來保證到達更細粒度的控制,可能這時候有人要說不是有string.Intern能夠進行字符串保留,達到相似的效果,對於這個我只想說內部肯定的字符串確實能夠達到這個效果,可是由於字符串保留機制就致使了gc不會對此進行回收,會致使若是外部輸入的string是不可控的狀況下就能夠給程序形成諸如oom的問題,對此我拋磚引玉但願各位博友能夠提出寶貴的意見或者建議,或者有現成的更好的解決方案也請分享一下,github地址下面貼代碼:git
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保證併發多線程安全且高效。github
如何使用數據庫
var key = "testKey"; var timeoutMillis = 3000; var acquired = false; try { acquired = MonitorStr.TryEnter(key, timeoutMillis); if (acquired) { //Do Something } else { throw new XXXTimeOutException(); } } finally { if(acquired) 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 { if(acquired) MonitorStr.Exit(key); } }
謝謝安全