Instance-controlled classes and multithreading

In Effective Java Chapter 2, Item 1, Bloch suggests considering static factory methods instead of constructors for initializing an object. Of the advantages he mentions is that this pattern allows classes to return the same object from repeated calls:

The ability of static factory methods to return the same object from repeated calls allows classes to maintain strict control over which instances exist at all times. The classes that do this say they are instance controlled. There are several reasons to write instance-controlled classes. Instance control allows a class to ensure that it is singleton (clause 3) or non-instance (clause 4). In addition, it allows an immutable class (clause 15) to ensure that no two equal instances exist: a.equals (b) if and only if a == b.

How does this pattern work in a multithreaded environment? For example, I have an immutable class that needs to be controlled by an instance, since only one entity with a given ID can exist at a time:

public class Entity {

    private final UUID entityId;

    private static final Map<UUID, Entity> entities = new HashMap<UUID, Entity>();

    private Entity(UUID entityId) {
        this.entityId = entityId;
    }

    public static Entity newInstance(UUID entityId) {
        Entity entity = entities.get(entityId);
        if (entity == null) {
            entity = new Entity(entityId);
            entities.put(entityId, entity);
        }
        return entity;
    }

}

      

What happens if I call newInstance()

from split threads? How can I make this class thread safe?

+3


source to share


2 answers


Make a sync newInstance that

public static synchronized Entity newInstance(UUID entityId){
     ...
}

      



so that a thread entering a new instance method cannot be called by any other thread unless the first thread exits. Basically what happens is the first thread acquires the lock for the entire class. During the time that the first thread holds the lock for the class, no other thread can enter a synchronous static method for that class.

+7


source


If you run this code, it can lead to unpredictable results, since two threads can call the newInstance method at the same time, both will see the field entity

as null, and both will create new Entity

. In this case, the two threads will have different instances of this class.

You should have a static entity of a private entity in your class, not getting it from a map. This is why you should use sync. You can sync the whole method like this:

public synchronized static Entity newInstance(UUID entityId)

      

Alternatively, you can use the Double Check Locking feature, which is better but needs to be done carefully - take a look at the comments below.

Regarding thread safety of this class, there is another matter - the card you are using. This makes the class Mutable because the state of the Entity changes when the map changes. In this case, the ending is not enough. You have to save the map in some other class like EntityManager.

I think that your organization should be simple and should not be interested in the question "I am unique" - it should be someone who has to. So I suggest that the Entity looks like this:



public class Entity {

    private final UUID entityId;

    public Entity(UUID entityId) {
        this.entityId = entityId;
    }

    public UUID getEntityId() {
        return entityId;
    }
}

      

Now he is immutable and will remain so, because his field is final and unchanging. If you want to add some fields, make sure they are immutable too.

As far as storage goes, I would suggest some kind of owner class:

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class EntityHolder {
    private static Map<UUID, Entity> entities;

    private static volatile EntityHolder singleton;

    public EntityHolder() {
        entities = new ConcurrentHashMap<UUID, Entity>();
    }

    public Entity getEntity(final UUID id) {
        return entities.get(id);
    }

    public boolean addEntity(final UUID id, final Entity entity) {
        synchronized (entities) {
            if (entities.containsKey(id)) {
                return false;
            } else {
                entities.put(id, entity);
                return true;
            }
        }
    }

    public void removeEntity(final UUID id) {
        entities.remove(id);
    }

    public static EntityHolder getInstance() {
        if (singleton == null) {
            synchronized (EntityHolder.class) {
                if (singleton == null) {
                    singleton = new EntityHolder(); 
                }
            }
        }
        return singleton;
    }
}

      

This way you have decoupled it from all other classes. And as far as creation goes, I would use the creator (factory) like this:

import java.util.UUID;

public class EntityCreator {

public static void createEntity(final UUID id) {
    boolean entityAdded = EntityHolder.getInstance().addEntity(id, new Entity(id));
    if (entityAdded) {
        System.out.println("Entity added.");
    } else {
        System.out.println("Entity already exists.");
    }
}
}

      

0


source







All Articles