学习Java单子设计模式

102 阅读9分钟

Java Singleton设计模式

单子是一种规则和程序的组合,它保证只有一个其类型的实体存在,并给其他代码一个单一的访问点。单子与全局变量有类似的优点和缺点。

它们相当有用但又违反了代码的模块化。我们使用这个模型框架来限制在Java虚拟机中可以创建的类的实例的数量。这个模型保证了只有一个类的实例存在。

单子设计模式的概述

术语单子指的是只有一个实例的组件。通过阻止一个类的构建,它保证在Java虚拟机中只有一个类的实体存在。

在Java中,一个Singleton类有利于只创建一个实例。这一个对象或实例提供对所有其他类的全局访问。

彼此唯一的字段和像类这样的特征,如唯一或静态组件,将只被使用一次。

使用单子类功能的目的

单子类型的基本目标之一是将创建的实体数量减少到只有一个。这保证了对组件的访问,如套接字或数据库连接,是可控的。

因为它限制了实例的创建,所以在使用单子类时不会有内存空间的浪费。因为这个对象只会被创建一次,而不是每当最新的提案被提交时都会被创建。

这个单一对象可以根据需求多次使用。这就是为什么Java中的Singleton设计常用于多线程和数据库操作中的缓存、调试、线程池和安全配置。

单子类的设计

对于我们来说,要设计一个单子类,我们需要以下内容。

  1. 一个private关键字来声明Singleton类的构造函数。我们把它定义为私有,这样其他类就不能用它来创建或实例化对象。
  2. 一个具有私有变量的类;这恰好是唯一具有实例类型的类。
  3. 构建一个静态main函数,该函数的返回类型是单子类的对象。

单子类与普通类的区别

  • 当涉及到实例化类的实体的技术时,Singleton类与其他类不同。Java构造函数创建一个标准的类。此外,如果我们想初始化一个单子类型的类对象,我们使用getInstance() 函数。
  • 一个普通的类会在应用程序生命周期的终端消失,而一个单子类则不会。

如果我们将我们的类创建为blueprint ,并添加引用,我们会有以下情况。

public class BluePrint
   {
    public static void main(String [] args)
    {
        blueprint p01 = new blueprint();
        blueprint p02 = new blueprint();
    }
}

当你创建多个实例时,两个引用变量的值将是不同的。因此,它将不会是一个单子类。

如果一个对象是空的,懒惰的,渴望的,设计趋势产生一个新的单子实体或任何特定的实体。

但是,如果实体不是空的,他们必须返回另一个单子实体对象或该对象的其他特定元素。我们将从急切加载开始,为此我们需要记住一些构建急切加载实体的准则。

用Eager加载单子对象

如果我们考虑制作一个Singleton类,关键词Singleton 是我们首先想到的。然而,Singleton是Java中的一个原则,只有在以下情况下才能创建。

  1. 一个私有类有一个默认的构造函数。
  2. 任何受保护的静态类类型的对象被声明为空值。
  3. 一个参数被分配给单子类类型的构造函数(就像我们在第二步做的那样)。
  4. 我们初始化一个公共静态getObjectgetInstance 函数,以类对象作为返回值。

下面是一个例子,说明我们如何实现上述创建对象的准则。

public class EagerInitializations
{

  private static EagerInitializations object = new EarlyInitializations();
  public String string;

      private EagerInitializations()
      {
        string = "LEARN TO USE EAGER INTIALIZATION";
       }
         public static EagerInitializations getInstances()
         {
          return object;
          }
          public static void main(String args[])
            {
             EagerInitializations text = EagerInitializations.getInstances();

             System.out.println("Initial string:");
             System.out.println(text.string);
             System.out.println("lower case string example");
             text.string = (text.string).toLowerCase();
             System.out.println(text.string);
            }
            private static class EarlyInitializations extends EagerInitialization
           {
             public EarlyInitializations()
           {
        }
    }
}

输出结果将是。

Initial string:

LEARN TO USE EAGER INITIALIZATION
lowercase string example

learn to use eager initialization

在上面的例子中,我们首先创建了一个私有默认构造函数。这是因为一个私有的构造函数将不允许创建新的对象。

第二步是创建一个私有的静态类类型变量。这将是一个静态变量,因为我们将不得不在函数中使用它来制造对象。

第三步是使用我们在第二步中获得的类类型变量来创建对象。

最后一步是结构化的getInstance 函数,使我们的对象成为单子类型。这是因为getInstance 函数将是一个静态函数,每次调用它时都只产生一个类的实例。

急于加载有一些缺点。一个是引用对象是一个静态类别,这意味着它将被创建,并在类被加载时在内存中可用。

这使得它成为一个全局变量,因为当任何类被加载时,这个对象在本地内存中是可用的。这是一个缺点,因为如果一个对象很笨重,它就会浪费内存和计算能力。

我们使用懒惰加载的概念来解决这个问题。值得注意的是,我们不能生成更多的Singleton实例,因为之前已经创建了一个对象。

加载懒惰的Singleton对象

我们已经看到,如果我们使用Eager Initialization 技术来创建单子类,就会导致构建一个不需要的对象,而不管应用程序是否使用它。

为了解决这个问题,我们可以使用Lazy Initialization 技术来构建一个单子类对象。懒惰初始化技术将类的创建推迟到需要它的时候。

懒惰初始化要求我们遵循以下程序。

  1. 使类的构造函数不被其范围外的任何其他函数访问。
  2. 创建一个不会被该类范围外的函数使用的类函数。
  3. 作为最后一步,创建一个工厂方法。这个方法决定实例属性是否为空。如果实例为空,将创建一个Singleton实例。否则,可用的实例将被返回。
public class UseLazyInitialization
  {
     private static UseLazyInitialization instances;
     public String strings;
     private UseLazyInitialization ()
         {
         string = "How to use lazy initialization";
           }
             public static UseLazyInitialization getInstances()
               {
              if (instances = null)
               {

               instances = new UseLazyInitialization ();
               }
              return instances;
             }
             public static void main(String [] args)
           {
           UseLazyInitialization message = UseLazyInitialization .getInstance();

           System.out.println("WE will show:");
           System.out.println(message.strings);
       }
    }

输出结果将是。

We will show:
How to use lazy initialization

通过在使用getInstance() 方法的时候构造一个对象,我们避免了急于加载的缺点。

在前面的例子中,第一步是建立一个私有的默认构造函数,因为受保护的构造函数方法会抑制'最近参数的生成。然后,建立一个受保护的静态值。

这必须是不可改变的数据,因为它在构造函数内构建对象时被利用。我们在第三个也是最后一个阶段构建一个getInstance() 函数,根据一个标准返回单子类对象。

懒惰加载中的线程同步化

如果我们有很多实例,我们必须使对象解决上述实现的缺陷。当我们第一次调用getInstance() 方法时,对象将为空,因此将创建一个新的对象。

如果我们再次调用它,已经创建的对象将被返回。在多线程环境下,单子类是通过线程安全的单子技术创建的。

getInstance() 方法必须被标记为同步的。当我们想防止许多线程同时访问这个函数时,我们会这样做。

class OurSingleton
   {
    protected static OurSingleton object1;

    protected OurSingleton() {}

    public static synchronized OurSingleton getInstances()
    {
        if (object1 == null) {
            object1 = new OurSingleton();
        }
        return object1;
    }
}

public class BluePrint
{
    public static void main(String args[])
     {
        Threads r1 = new Threads(new Runables()
         {
            public void run()
            {
                OurSingleton a1 = OurSingleton.getInstances();
            }
        });

        Threads r2 = new Threads(new Runnables()
         {
            public void run()
            {
                OurSingleton a1 = OurSingleton.getInstances();
            }
        });

        r1.start();
        r2.start();
    }
}

在上面的代码中,指定了两个线程r1和r2,并且都调用了同一个程序。它们在同一时间被初始化。我们应该注意,当我们有一个线程时,我们应该在getgetInstance 方法的帮助下使用同步化。

然而,让一个方法同步化会导致大量的工作,因为getInstance() 函数会执行大量的工作。此外,它还会以大约一百的比例降低性能,这就是同步线程的问题所在。

使用线程双重检查锁定技术的懒惰加载

为了解决前面的方法在时间上的复杂性的缺点,我们可以使用双重检查锁定的想法。

它需要对对象的值进行两次检查。

  • 简单地-如果(对象==空)
  • 在同步块中

我们可以在生成对象的同时实现同步线程。这是为了通过同步getInstance 函数来削减工期的复杂性。

因此,我们可以等待的时间少至约三到四毫秒,而不是等待约七十毫秒。

下面的例子显示了如何使用线程双重检查的锁定技术。

class OurSingleton {
    private static OurSingleton object1;

    private OurSingleton() {}

    public static synchronized OurSingleton getInstances()
    {
        if (object1 == null)
         {
            synchronized(OurSingleton.class)
            {
                if (object1 == null)
                    object1 = new OurSingleton();
            }
        }
        return object1;
    }
}

public class BluePrint
{
    public static void main(String args[])
    {
        Threads r1 = new Threads(new Runnable() {
            public void run() {
                OurSingleton a1 = OurSingleton.getInstances();
            }
        });

        Threads r2 = new Threads(new Runnable()
        {
            public void run() {
                OurSingleton a1 = OurSingleton.getInstances();
            }
        });

        r1.start();
        r2.start();
    }
}

在上面的例子中,我们找到了一个解决同步代码开销问题的方法。这个函数的GetInstance 是不同步的,但创建实例的组件是同步的。因此,只有少数线程必须在第一时间等待。

带有Singleton模式的枚举

这是一种创建单子模式的方法,它克服了我们所看的所有实现方式的缺点。

从Java 1.5开始,只有一种创建单子设计模式的技术是线程安全的,而且使用的资源较少。这种方法只有在使用Java 1.5或更高版本时才能发挥作用。

下面是一个具有单子模式的枚举的语法。

 enum IJK
  {
      Instance; //private constructor (which should be inbuilt)
  }

下面是一个例子。

enum OurSingleton {
    INSTANCE;
    int y;
    public void show() {
        System.out.println(y);
    }
}

public class BluePrint
 {
    public static void main(String args[])
    {
        OurSingleton object2 = OurSingleton.INSTANCE;
        object2.y = 15;
        object2.show();

        OurSingleton object2 = OurSingleton.INSTANCE;
        object3.y = 25;
        object2.show();
    }
}

这将是一个结果。

15
25

当我们使用枚举时,有一个方法叫做readResolve ,它不会生成一个新的对象,而是使用现有的对象。

总结

在本教程中,我们已经看到Singleton类中只有一个项目。我们可以使用私有构造函数和getInstance 构造函数来创建一个Singleton。

我们还讨论了如何使用急切和懒惰加载技术来构建一个单子设计模式。以及,线程,双重检查锁,枚举,以及单子类和普通类的区别。