为什么这段代码不是线程安全的?

时间:2013-09-24 21:57:41

标签: c# multithreading thread-safety thread-synchronization

我有一段这样的代码:

public class UserCache
{
    private Dictionary<int, User> _users = new Dictionary<int, User>();

    public User GetUser(int id)
    {
        User u = null;

        lock (_users)
        {
            if (_users.containsKey(id))
                return _users[id];
        }

        //The below line is threadsafe, so no worries on that.
        u = RetrieveUser(id); // Method to retrieve from database;

        lock (_users)
        {
            _users.Add(id, u);
        }

        return u;
    }
}

我正在锁定对字典的访问权限,但是我的团队中有人告诉它仍然没有线程安全(没有解释)。问题是 - 你认为这是线程安全的吗?

编辑:忘了询问,解决方案会是什么样子。请注意我没有想要锁定整个方法,因为检索用户是一个耗时的操作。

4 个答案:

答案 0 :(得分:9)

不,它不是线程安全的。想象一下,它使用相同的ID同时调用两次,这在以前不存在。

两个线程都会达到RetrieveUser,并且两个都会调用_users.Add(id, u)。第二个调用将失败,因为密钥已存在于字典中。

(顺便说一下,为了便于阅读,我强烈建议使用大括号来锁定if语句等。)

答案 1 :(得分:2)

在某种意义上它是线程安全的,它不会破坏任何数据结构。从整个方法原子行为的意义上说,它不是线程安全的。两个线程可能会找到要丢失的项目,然后创建它然后添加它。其中一个加法器会失败。

答案 2 :(得分:0)

我不认为你在这里有更多的选择,因为_users.Add(id,u)的结果依赖于RetrieveUser,你必须锁定所有方法以使其线程安全。可能是约翰双向飞碟可以证实这一点 解决方案可能看起来像这样

public class UserCache
 {
  private Dictionary<int, User> _users = new Dictionary<int, User>();
 private readonly object _syncLock = new object(); 
 public User GetUser(int id)
{
   User u = null;

    lock (_syncLock)
    { 

        if (_users.containsKey(id))
            return _users[id];


    //The below line is threadsafe, so no worries on that.
    u = RetrieveUser(id); // Method to retrieve from database;


        _users.Add(id, u);
    }

    return u;
}

}

希望这个帮助

答案 3 :(得分:-3)

我认为您必须应用单例模式才能拥有真正的线程安全代码。在这一刻,你可以有2个班级实例。