简体   繁体   中英

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. 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. 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.
  • 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. 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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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