在 Effective Java 第2章,第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()
会怎样?如何让这个类线程安全?
答案 0 :(得分:8)
使newInstance同步
public static synchronized Entity newInstance(UUID entityId){
...
}
这样一个线程进入新的实例方法,除非第一个线程完成,否则没有其他线程可以调用此方法。基本上发生的是第一个线程获得整个类的锁。对于第一个线程持有类的锁的时间,没有其他线程可以为该类输入同步的静态方法。
答案 1 :(得分:0)
如果运行此代码,可能会导致不可预测的结果,因为两个线程可以同时调用newInstance方法,两者都会将entity
字段视为null,并且两者都会创建new Entity
。在这种情况下,这两个线程将具有此类的不同实例。
您的类中应该有一个静态私有字段Entity实体,而不是从地图中获取它。 这就是你应该使用同步的原因。您可以像这样同步整个方法:
public synchronized static Entity newInstance(UUID entityId)
作为替代方案,你可以使用Double Check Locking,这是更好的,但必须仔细完成 - 看看下面的评论。
关于这门课程的线程安全还有另外一件事 - 你正在使用的地图。它使类成为Mutable,因为当更改地图时,Entity对象的状态会发生变化。在这种情况下,最终是不够的。 您应该将地图存储在EntityManager等其他类中。
我认为您的实体应该很简单,不应该对这个问题感兴趣,我是不是唯一的&#39; - 它应该是某人的职责。这就是为什么我会建议实体看起来像这样:
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.");
}
}
}