繁体   English   中英

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

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

我正在编写一个装饰器来实现缓存。 该对象将由我的DI容器注册为单例。 因为我知道我将对象注册为单例,所以表示我的缓存的字段不是静态的。 我不确定这是否是最佳做法,但我正竭尽全力避免锁定。 我的缓存被延迟初始化,并且一次仅运行一次就很昂贵/昂贵。 我的问题是我是否需要担心多个线程运行缓存初始化逻辑? 我的直觉告诉我“是的,我确实需要担心”,但是我听过其他开发人员说“如果不是静态的,则没有意义进行锁定”。

//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];
    }
}

是的,您需要锁定以防止多个线程运行相同的代码。

“如果不是静态的,则锁定没有意义”

仅当每个线程都有自己的类实例时才适用。 在线程之间共享实例后,就需要同步访问。

Guffa是对的。 此时无须补充。 我要添加的是一些重构。 您可能应该从装饰器中提取缓存行为,如下所示:

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;
    }
}

这里需要注意的几件事:

  • 装饰器不应创建查询,而只需将传入的查询消息传递给其装饰对象。
  • 您可能想创建一个通用的缓存装饰器; 这使您可以将其应用于多个处理程序。
  • 延迟的使用是没有用的,因为创建Dictionary<,>确实是轻量级的,并且总是创建字典。
  • 如果使装饰器通用,则需要另一种确定缓存键的方法。 过去对我有用的是将完整的查询消息序列化为JSON(使用JSON.NET)并将其用作键。 您可能希望对结果执行相同的操作,因为这些结果是可变对象( Foo和数组都是),这使得在多个线程上重用它们很危险(您永远都不知道谁会更改它们)。

暂无
暂无

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

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