如何在Java中正确地创建一个线程安全的Singleton Factory?

How to correctly make a thread safe Singleton Factory in Java?

本问题已经有最佳答案,请猛点这里访问。

这是我第一次写Factory类。下面是我的工厂类,我不确定这是否是使线程安全的singleton工厂类的正确方法。我将返回使用此工厂的客户机实例?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ClientFactory {

    private static ClientFactory instance = null;  

    private ClientFactory() {

    }

    public static ClientFactory getInstance() {

        if (instance == null)
        {
            instance =  new ClientFactory();
        }

        return instance;
    }

    public IClient getClient() {

        return new TestClient();
    }
}

这是我的testclient类-

1
2
3
4
public class TestClient implements IClient {


}

这就是我如何使用我的工厂-

1
IClient client = ClientFactory.getInstance().getClient();


实际上,您的工厂不是线程安全的,因为在竞争条件下,您可以在应用程序中有多个clientFactory。假设两个线程:

  • threada正在计算条件"if(instance==null)",instance为空,因此它进入语句
  • threadb正在计算条件"if(instance==null)",而instance为空(因为a没有进行实例化),因此它进入语句
  • threada创建新的clientFactory()并返回它
  • threadb创建新的clientFactory()并返回它
  • 现在我们在应用程序中有多个客户端工厂。当然,稍后尝试检索实例的其他线程将始终返回单个实例。
  • 在我看来,在Java中编写单例最简单的方法就是使用EnUM。在您的情况下,它将看起来:

    1
    2
    3
    4
    5
    6
    7
    public enum ClientFactory {
      INSTANCE;

      public Company getClient() {
        return new Company();
      }
    }

    用途:

    1
    ClientFactory.INSTANCE.getClient()


    wiki上的线程安全实现(示例)-wikipedia上的singleton模式

    在上面的链接中,对于支持枚举的任何Java来说,单个元素EDCOX1×0×类型是实现单体的最佳方式。

    最好但简单的一个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ClientFactory{
        private ClientFactory() {}

        private static ClientFactory INSTANCE=null;

        public static ClientFactory getInstance() {
            if(INSTANCE==null){
                synchronize(ClientFactory.class){
                    if(INSTANCE==null) // check again within synchronized block to guard for race condition
                        INSTANCE=new ClientFactory();
                }
            }
            return INSTANCE;
        }
    }

    Source:维基百科


    单件和工厂是不同的东西。要构造一个singleton属性,我想您可以将它的getInstance()方法视为一个工厂。工厂制造"东西"。单件意味着在任何时候,这些"事物"中只有0或1存在。

    如果您试图创建一个合适的单线程,那么在Java中以线程安全的方式执行这一操作是非常麻烦的。如果没有同步或其他线程安全对策,上面列出的代码在检查周围有一个微妙的竞争条件,然后将代码设置为初始化clientfactory实例变量。有两种方法可以解决这个比赛条件。你选择哪种方式很大程度上取决于通过clientFactory构造函数的代价。我的构造器通常是轻量级的,所以我选择了避免同步需要的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class ClientFactory {
        private static final ClientFactory instance = new ClientFactory();

        private ClientFactory() { }

        public static ClientFactory getInstance() {
            return instance;
        }
    }

    如果您希望在构造中"懒惰",在有人显式调用getInstance()之前不进行构建,那么现在需要同步以避免竞争条件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class ClientFactory {
        private static ClientFactory instance = null;

        private ClientFactory() { }

        public static synchronized ClientFactory getInstance() {
            if ( instance == null ) {
                instance = new ClientFactory();
            }
            return instance;
        }
    }


    你的工厂是一个完美的单体(只是它不是线程安全的)。


    clientFactory是一个工厂,但既不是单实例工厂,也不是线程安全工厂。在任何时候,当调用clientFactory.getInstance().getLinet()时,它将返回一个新的实例,所以它不是绝对的单例工厂。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private IClient iclient;

    public IClient getClient() {

        if ( iclient == null ){
             iclient = new TestClient();
        }

        return iclient ;
    }

    那么工厂是一个单例工厂,但它不是线程安全的。假设有多个线程调用GetInstance,所有线程都将找到客户端工厂实例为空,因此它们将分别构造该实例,问题与getclient()方法相同。

    很容易修复它,您可以声明这两个方法是同步的。

    首先,如果您真的想使用factory parrtern,不要忘记隐藏客户的构造函数。

    1
    2
    private TestClient(){
    }