实例控制类和多线程 在有效的java第2章中,第1条布洛赫建议考虑静态工厂方法而不是构造函数来初始化对象。他提到的一个好处是,这种模式允许类从重复调用中返回相同的对象:

实例控制类和多线程 在有效的java第2章中,第1条布洛赫建议考虑静态工厂方法而不是构造函数来初始化对象。他提到的一个好处是,这种模式允许类从重复调用中返回相同的对象:,java,multithreading,multiton,Java,Multithreading,Multiton,静态工厂方法从重复调用中返回相同对象的能力允许类在任何时候对存在的实例保持严格控制。这样做的类称为实例控制类。编写实例控制类有几个原因。实例控件允许类保证它是单实例(第3项)或不可实例化(第4项)。此外,它允许不可变类(第15项)保证不存在两个相等的实例:a.equals(b)当且仅当a==b 这种模式在多线程环境中如何工作?例如,我有一个应该由实例控制的不可变类,因为一次只能存在一个具有给定ID的实体: public class Entity { private final UUID

静态工厂方法从重复调用中返回相同对象的能力允许类在任何时候对存在的实例保持严格控制。这样做的类称为实例控制类。编写实例控制类有几个原因。实例控件允许类保证它是单实例(第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;
    }

}
公共类实体{
私有最终UUID实体ID;
私有静态最终映射实体=新HashMap();
私有实体(UUID entityId){
this.entityId=entityId;
}
公共静态实体newInstance(UUID entityId){
实体=entities.get(entityId);
if(实体==null){
实体=新实体(entityId);
实体。put(实体ID,实体);
}
返回实体;
}
}

如果从分离的线程调用
newInstance()
,会发生什么?如何使此类线程安全?

使newInstance同步

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

因此,一个线程进入新实例方法,除非第一个线程完成,否则其他线程不能调用此方法。基本上,第一个线程获得整个类的锁。当第一个线程持有类的锁时,没有其他线程可以为该类输入同步静态方法。

如果运行此代码,可能会导致不可预测的结果,因为两个线程可以同时调用newInstance方法,两者都将看到
实体
字段为空,并且都将创建
新实体
。在这种情况下,这两个线程将具有该类的不同实例

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

public synchronized static Entity newInstance(UUID entityId)
作为一种替代方案,您可以使用双重检查锁定,这是更好的,但必须小心地执行-查看下面的注释

至于这个类的线程安全性,还有另一个问题——您正在使用的映射。它使类可变,因为映射更改时实体对象的状态会更改。在这种情况下,最终结果是不够的。 您应该将地图存储在其他类中,如EntityManager

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

public class Entity {

    private final UUID entityId;

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

    public UUID getEntityId() {
        return entityId;
    }
}
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.");
    }
}
}
现在它是不可变的,并且将保持不变,因为它的字段是最终的且不可变的。如果要添加一些字段,请确保这些字段也是不可变的

至于存储,我建议采用以下类别:

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

相关的,你说的是模式。这里有一个很好的Java示例。不要忘记,如果不再从内部映射中删除实体,这将是一个潜在的内存泄漏!根据您的用例,销毁实体也可能是必要的@更好的是,保留
WeakReference
s,它仍然保证每个guid最多存在一个活动的
实体
实例,但是垃圾收集很快。谢谢-我没有意识到这一点。我已经从我的答案中删除了DCL。是的。至少我的编译器允许我这样做;)同步静态方法时,使用类进行锁定。我再次编辑了我的帖子,以便它以更详细的方式回答问题。我已经使用了DCL的volatile-谢谢你提醒我。如果您认为扩展的答案有问题,请务必告诉我。在普通同步块上使用这个过于复杂的DCL修复程序有什么好处?如果您认为它更快,请运行一个性能测试-我怀疑它在常用的硬件架构上更快。好的,我会试试。有人向我建议速度更快,对我来说有点道理,所以我没有检查,但现在我会检查。然而,我认为如果在该类中有其他同步方法,它将是有用的。谢谢你的评论。