Flyweight Concurrency

Thinking about Flyweight today. The wikipedia entry said nothing about concurrency (I have now contributed the content in this post to the article) and the java example is flawed in a multi-threaded context as it is possible to create two flavour instances for the same flavour. To make matters worst the Java CoffeeFlavour does not deal with equality and thus two separate instances of mocha are not equal. This is less than ideal as the whole point of Flyweight is to enable objects to be shared, which more often than not means between threads as well as clients.

In DDD parlance Flyweight types are by definition value types. They are not entities, they have no unique identity, they are immutable and one instance is equal to another instance of the same value. Here is a C# implementation of CoffeeFlavour which satisfies this criteria:


public class CoffeeFlavour
{
    private readonly string _flavour;

    public CoffeeFlavour(string flavour)
    {
        _flavour = flavour;
    }

    public string Flavour
    {
        get { return _flavour; }
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is CoffeeFlavour && Equals((CoffeeFlavour)obj);
    }

    public bool Equals(CoffeeFlavour other)
    {
        return string.Equals(_flavour, other._flavour);
    }

    public override int GetHashCode()
    {
        return (_flavour != null ? _flavour.GetHashCode() : 0);
    }

    public static bool operator ==(CoffeeFlavour a, CoffeeFlavour b)
    {
        return Equals(a, b);
    }

    public static bool operator !=(CoffeeFlavour a, CoffeeFlavour b)
    {
        return !Equals(a, b);
    } 
}

and tests:


[TestFixture]
public class CoffeeFlavourTests
{
    [Test]
    public void Equality()
    {
        var coffeeFlavourA = new CoffeeFlavour("Mocha");
        var coffeeFlavourB = new CoffeeFlavour("Mocha");
        var coffeeFlavourC = new CoffeeFlavour("Almond");

        //Identity of this value type is of no concern.  
        coffeeFlavourA.Equals(coffeeFlavourB).Should().BeTrue();
        coffeeFlavourB.Equals(coffeeFlavourC).Should().BeFalse();
    }

    [Test]
    public void EqualityOperators()
    {
        var coffeeFlavourA = new CoffeeFlavour("Mocha");
        var coffeeFlavourB = new CoffeeFlavour("Mocha");
        var coffeeFlavourC = new CoffeeFlavour("Almond");

        (coffeeFlavourA == coffeeFlavourB).Should().BeTrue();
        (coffeeFlavourA == coffeeFlavourC).Should().BeFalse();

        (coffeeFlavourA != coffeeFlavourB).Should().BeFalse();
        (coffeeFlavourA != coffeeFlavourC).Should().BeTrue();
    }  
}

Now with regard to creating our instances, we have options. If we know all of the possible values in advance we can create them all ahead of time. The alternative is dynamic flyweight instantiation in which case we can choose between having the minimum memory footprint with some thread contention or no thread contention with the possibility of having more than one instance per value. The latter is only viable because our flyweight is a true value type, has overridden equals and overloaded the == and != operators.

Example in C# of two factory implementations, the names of which are self-explanatory:


using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;

public interface ICoffeeFlavourFactory
{
    CoffeeFlavour GetFlavour(string flavour);
}

public class ReducedMemoryFootprint : ICoffeeFlavourFactory
{
    private readonly object _cacheLock = new object();
    private readonly IDictionary<string, CoffeeFlavour> _cache = new Dictionary<string, CoffeeFlavour>();

    public CoffeeFlavour GetFlavour(string flavour)
    {
        if (_cache.ContainsKey(flavour))
            return _cache[flavour];

        var coffeeFlavour = new CoffeeFlavour(flavour);

        ThreadPool.QueueUserWorkItem(AddFlavourToCache, coffeeFlavour);

        return coffeeFlavour;
    }

    private void AddFlavourToCache(object state)
    {
        var coffeeFlavour = (CoffeeFlavour)state;
        if (_cache.ContainsKey(coffeeFlavour.Flavour)) return;

        lock (_cacheLock)
        {
            if (!_cache.ContainsKey(coffeeFlavour.Flavour))
                _cache.Add(coffeeFlavour.Flavour, coffeeFlavour);
        }
    }
}

public class MinimumMemoryFootprint : ICoffeeFlavourFactory
{
    private readonly ConcurrentDictionary<string, CoffeeFlavour> _cache = new ConcurrentDictionary<string, CoffeeFlavour>();

    public CoffeeFlavour GetFlavour(string flavour)
    {
        return _cache.GetOrAdd(flavour, flv => new CoffeeFlavour(flv));
    }
}

Download source code here.

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s