对线程安全理解的例子
随着网站访问量的增加,在线用户实体信息的存储方式变得重要起来。存储在线用户的信息一般有这三种方案:
1、用户的实体信息保存在Session里,简单方便,随着Session的过期用户信息自动过期。
2、用户信息保存在数据库中,用一个表存储在线的用户信息。
3、用户信息保存在内存。
当前项目用的是第一种方法,把用户的实体信息保存在Session中,虽然使用方便,但总感觉很别扭。Discuz!NT使用的是第二种方法,把在线用户标识保存在一个表中,从cookie跟读取用户的ID,并从用户信息表查询该用户的信息,组装到实体中。如果有大量的用户在线同时操作时,这也不是一个很好的解决办法。
这里选择第三种解决方案,把用户信息保存到内存。
我们使用Dictionary来存储用户信息,由于Dictionary不是线程安全的,因此需要注意只能单线程更新字典。
先定义一个保存用户信息的实体:
internal class UserEntity<T>{ /// <summary> /// 用户信息 /// </summary> internal T UserInfo { get; set; } /// <summary> /// 添加到列表的时间戳 /// </summary> internal DateTime Timestamp { get; set; }}
使用泛型包装用户实体,并增加一个时间戳,表示该用户信息添加到内存的时间,过期是根据这个时间来判断的。
再增加一个在线用户信息管理类:
/// <summary>/// 在线用户缓存管理/// </summary>/// <typeparam name="T"></typeparam>public class UserCacheManager<T>{ #region 静态属性 /// <summary> /// 静态用户缓存表 /// </summary> private static Dictionary<long, UserEntity<T>> _UserList = new Dictionary<long, UserEntity<T>>(); /// <summary> /// 过期时间 /// </summary> private static int _ExpiredMinutes = 30; /// <summary> /// 定时器 /// </summary> private static Timer _Timer = null; #endregion #region 静态构造函数 /// <summary> /// 静态构造函数 /// 初始化计时器 /// </summary> static UserCacheManager() { _Timer = new Timer(new TimerCallback(TimerClear), null, 60000, _ExpiredMinutes * 60000); } #endregion #region 私有方法 /// <summary> /// 清除在线用户 /// </summary> /// <param name="sender"></param> private static void TimerClear(object sender) { ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncClear)); } /// <summary> /// 异步清除过期的在线用户 /// </summary> /// <param name="sender"></param> private static void AsyncClear(object sender) { //当前时间 DateTime timestamp = DateTime.Now.AddMinutes(0 - _ExpiredMinutes); //过期的用户列表 var expiredUserList = (from userEntity in _UserList where userEntity.Value.Timestamp <= timestamp select userEntity); if (expiredUserList != null && expiredUserList.Count() > 0) { List<long> expiredUserIdentities = expiredUserList.Select(o => o.Key).ToList(); lock (_UserList) { foreach (long userId in expiredUserIdentities) _UserList.Remove(userId); } } } #endregion #region 公共方法 /// <summary> /// 增加在线用户 /// </summary> /// <param name="userIdentity">用户身份标识</param> /// <param name="userInfo">用户实体</param> public static void Add(long userIdentity, T userInfo) { lock (_UserList) { #region 创建用户实体 UserEntity<T> userEntity = new UserEntity<T> { Timestamp = DateTime.Now, UserInfo = userInfo }; #endregion if (_UserList.Keys.Contains(userIdentity)) { _UserList[userIdentity] = userEntity; } else { _UserList.Add(userIdentity, userEntity); } } } /// <summary> /// 获取用户信息 /// </summary> /// <param name="userIdentity"></param> /// <returns></returns> public static T Get(long userIdentity) { lock (_UserList) { if (_UserList.Keys.Contains(userIdentity)) { _UserList[userIdentity].Timestamp = DateTime.Now; return _UserList[userIdentity].UserInfo; } else { return default(T); } } } /// <summary> /// 移除用户缓存信息 /// </summary> /// <param name="userIdentity"></param> public static void Remove(long userIdentity) { if (_UserList.Keys.Contains(userIdentity)) { lock (_UserList) { _UserList[userIdentity].Timestamp = DateTime.Now.AddDays(-1); } } } #endregion}
Dictionary<long, UserEntity<T>> _UserList用来保存在线的用户列表。
在静态构造函数中声明了一个定时器,定时器负责清理过期的用户信息。并把清理用户信息的方法装入线程池执行。
MSDN:只要不修改Dictionary,Dictionary就可以同时支持多个阅读器。即便如此,从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。当出现枚举与写访问互相争用这种极少发生的情况时,必须在整个枚举过程中锁定集合。若允许多个线程对集合执行读写操作,您必须实现自己的同步。
所以在更新Dictionary中,都锁定了字典,防止多线程冲突。
源代码在经过富文本编辑器后显示有点问题,感兴趣的朋友可以 从这里下载源码
http://blog.moozi.net/archives/onlineusercachemanager/
http://msdn.microsoft.com/zh-cn/library/c5kehkcz(VS.80).aspx
作者:ChenLuLouis
出处:http://www.cnblogs.com/chenlulouis/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
该文章也同时发布在我的独立博客中-chenlulouisBlog。
posted on 2010-05-25 00:27 chenlulouis 阅读(598) 评论(0) 编辑 收藏 举报