5

Effective Java Chapter 2,Item 1 Bloch 建议考虑静态工厂方法而不是构造函数来初始化对象。他提到的好处之一是这种模式允许类从重复调用中返回相同的对象:

静态工厂方法从重复调用返回相同对象的能力允许类在任何时候保持对存在的实例的严格控制。执行此操作的类被称为实例控制的。编写实例控制类有几个原因。实例控制允许一个类保证它是单例(第 3 项)或不可实例化(第 4 项)。此外,它允许不可变类(第 15 条)保证不存在两个相等的实例:a.equals(b) 当且仅当 a==b 时。

这种模式在多线程环境中如何工作?例如,我有一个不可变的类,它应该是实例控制的,因为一次只能存在一个具有给定 ID 的实体:

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

}

如果我newInstance()从分离的线程调用会发生什么?我怎样才能使这个类线程安全?

4

2 回答 2

9

使 newInstance 同步,即

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

这样一个线程进入新的实例方法,除非第一个线程完成,否则没有其他线程可以调用此方法。基本上发生的事情是第一个线程获得了整个类的锁。对于第一个线程持有该类的锁的时间,没有其他线程可以进入该类的同步静态方法。

于 2014-08-17T22:11:08.770 回答
0

如果您运行此代码,可能会导致不可预知的结果,因为两个线程可以同时调用 newInstance 方法,两者都会将该entity字段视为 null 并且都会创建new Entity. 在这种情况下,这两个线程将具有此类的不同实例。

您的类中应该有一个静态私有字段 Entity 实体,而不是从地图中获取它。这就是为什么你应该使用同步。您可以像这样同步整个方法:

public synchronized static Entity newInstance(UUID entityId)

作为替代方案,您可以使用更好的双重检查锁定,但必须小心操作 - 请查看下面的评论。

至于这个类的线程安全还有另一件事——你正在使用的地图。它使类 Mutable,因为 Entity 对象的状态在映射更改时会更改。在这种情况下,Final 是不够的。您应该将地图存储在其他类中,例如 EntityManager。

我认为你的实体应该很简单,不应该对“我是独一无二的”这个问题感兴趣——这应该是别人的职责。所以这就是为什么我建议实体看起来像这样:

public class Entity {

    private final UUID entityId;

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

    public UUID getEntityId() {
        return entityId;
    }
}

现在它是不可变的并且将保持这种状态,因为它的字段是最终的且不可变的。如果您想添加一些字段,请确保这些字段也是不可变的。

至于存储,我建议一些持有者类:

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

这样,您就可以将其与所有其他类分开但可以访问。至于创建我会使用这样的创建者(工厂):

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.");
    }
}
}
于 2014-08-17T22:22:14.547 回答