简体   繁体   English

如果没有静态字段,我是否需要担心对象是线程安全的?

[英]Do I need to worry about an object being thread safe if it has no static fields?

I'm writing a decorator to implement caching. 我正在编写一个装饰器来实现缓存。 The object will be registered as a singleton by my DI container. 该对象将由我的DI容器注册为单例。 Because I know I'm registering my object as a singleton, the field representing my cache is not static. 因为我知道我将对象注册为单例,所以表示我的缓存的字段不是静态的。 I'm not sure if this is best practice, but I'm trying to avoid locking at all cost. 我不确定这是否是最佳做法,但我正竭尽全力避免锁定。 My cache is lazily initialized and an expensive/run only once operation. 我的缓存被延迟初始化,并且一次仅运行一次就很昂贵/昂贵。 My question is do I need to worry about multiple threads running the cache initialization logic? 我的问题是我是否需要担心多个线程运行缓存初始化逻辑? My gut tells me "yes I do need to worry", but I've heard other devs say "No point in locking if it's not static". 我的直觉告诉我“是的,我确实需要担心”,但是我听过其他开发人员说“如果不是静态的,则没有意义进行锁定”。

//SimpleInjector DI Container configuration
public static class Bootstrapper
{
    public static void ConfigureContainer(Container container)
    {
        container.Register<IQueryHandler<GetFoos, Foo[]>, GetFoosHandler>(Lifestyle.Singleton);
        container.RegisterDecorator<IQueryHandler<GetFoos, Foo[]>, GetFoosCachingHandler>(Lifestyle.Singleton);
    }
}

public class Foo
{
    public int Id;
    public string FooTypeCode;
    public string Name;
}

public class GetFoos : IQuery<Foo[]>
{
    public string FooTypeCode;
}

public class GetFoosCachingHandler : IQueryHandler<GetFoos, Foo[]>
{
    private Lazy<Dictionary<string, Foo[]>> _cache;

    private readonly IQueryHandler<GetFoos, Foo[]> _queryHandler;

    public GetFoosCachingHandler(IQueryHandler<GetFoos, Foo[]> queryHandler)
    {
        _queryHandler = queryHandler;

        _cache = new Lazy<Dictionary<string, Foo[]>>(() =>
        {
            //expensive and run only once operation e.g. subscribe to bus for cache invalid messages and reset cache

            return new Dictionary<string, Foo[]>();
        });
    }

    public Foo[] Handle(GetFoos query)
    {
        var cache = _cache.Value;

        if (!cache.ContainsKey(query.FooTypeCode))
        {
            cache[query.FooTypeCode] = _queryHandler.Handle(new GetFoos { FooTypeCode = query.FooTypeCode });
        }

        return cache[query.FooTypeCode];
    }
}

Yes, you need locking to prevent multiple threads from running the same code. 是的,您需要锁定以防止多个线程运行相同的代码。

"No point in locking if it's not static" “如果不是静态的,则锁定没有意义”

That only applies when each thread has its own instance of a class. 仅当每个线程都有自己的类实例时才适用。 As soon as you share an instance between threads, you need to synchonise the access. 在线程之间共享实例后,就需要同步访问。

Guffa is right. Guffa是对的。 Nothing to add at this point. 此时无须补充。 What I would like to add is a little refactoring. 我要添加的是一些重构。 You should probably extract the caching behavior from the decorator as follows: 您可能应该从装饰器中提取缓存行为,如下所示:

public class GetFoosCachingHandler : IQueryHandler<GetFoos, Foo[]>{
    private readonly ICache _cache;
    private readonly IQueryHandler<GetFoos, Foo[]> _queryHandler;

    public GetFoosCachingHandler(ICache cache, IQueryHandler<GetFoos, Foo[]> queryHandler){
        _cache = cache;
        _queryHandler = queryHandler;
    }

    public Foo[] Handle(GetFoos query) {
        var result = _cache.Load<Foo[]>(query.FooTypeCode);

        if (result == null) {
            _cache.Store<Foo[]>(query.FooTypeCode, result = _queryHandler.Handle(query));
        }

        return result;
    }
}

A few things to note here: 这里需要注意的几件事:

  • A decorator should not create a query, but simply pass on the incoming query message to its decoratee. 装饰器不应创建查询,而只需将传入的查询消息传递给其装饰对象。
  • You might want to create a generic caching decorator instead; 您可能想创建一个通用的缓存装饰器; this allows you to apply it to multiple handlers. 这使您可以将其应用于多个处理程序。
  • The use of the Lazy is useless, because creating a Dictionary<,> is really lightweight and the dictionary is always created. 延迟的使用是没有用的,因为创建Dictionary<,>确实是轻量级的,并且总是创建字典。
  • If you make the decorator generic, you need a different way of determining the cache keys. 如果使装饰器通用,则需要另一种确定缓存键的方法。 What has worked out great for me in the past is to serialize the complete query message to JSON (using JSON.NET) and use that as key. 过去对我有用的是将完整的查询消息序列化为JSON(使用JSON.NET)并将其用作键。 You might want to do the same with the result, because those results are mutable objects (both Foo is and array is), making it dangerous to reuse them on multiple threads (you never know who changes them). 您可能希望对结果执行相同的操作,因为这些结果是可变对象( Foo和数组都是),这使得在多个线程上重用它们很危险(您永远都不知道谁会更改它们)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM