vlambda博客
学习文章列表

『设计模式』我就要一个对象,你别给我这么多好不好!---单例模式

引入

作为一个现代社会文明青年,我觉得一夫一妻制非常合理。有些男人富裕了点,就想多照顾几个女人的行为,真的不可取,有的时候法律在这些面前显得难以生效,毕竟重婚罪又不能限制婚外情,多找几个对象。人尚且如此,何况程序呢,面对只能实例化一个对象的程序,我们该如何处理呢?我们今天就来看一下单例模式!

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

目的:

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:

一个全局使用的类频繁地创建与销毁。

何时使用:

当您想控制实例数目,节省系统资源的时候。

优点

实例控制:单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。灵活性:因为类控制了实例化过程,所以类可以灵活更改实例化过程。

缺点

开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。可能的开发混淆:使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。对象生存期:不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。不符合单一职责原则没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

实现:

单例模式的 UML 图

创建一个 Singleton 类

package 单例模式;
public class Singleton { private static Singleton singleobj;
private Singleton() {}//通过private限定,不能通过new建立对象,也不能继承(可以有继承操作,但是无意义) public Singleton newinstance() { if(singleobj!=null) return singleobj; else { singleobj=new Singleton(); return singleobj; } } public void dosomething() { System.out.println("一个女朋友就够了!!!"); } }

从 singleton 类获取唯一的对象

package 单例模式;
public class SingletonTest {
public static void main(String[] args) { // SingleObject object = new SingleObject(); // 编译时错误:构造函数 SingleObject() 是不可见的
Singleton SingleT = Singleton.newinstance(); //获取唯一可用的对象 SingleT.dosomething();
}
}

执行程序,输出结果:

一个女朋友就够了!!!

进阶

看完前面的实列,但是并不能真正的应用在实际应用中,因为在现实的中更多的涉及到线程的问题,所以,给大家展示一下以下的方法。

1. 懒汉式(线程不安全,不支持多线程)

对比:

  • 是否 Lazy 初始化:是
  • 是否多线程安全:否
  • 实现难度:易

描述: 这种方式是最基本的实现方式,也就是前面提到的方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作,在多线程系统的今天,一般很少使用。

实例

package 单例模式;
public class Singleton { private static Singleton singleobj; private Singleton() {}//通过private限定,不能通过new建立对象,也不能继承(可以有继承操作,但是无意义) public Singleton newinstance(){ if(singleobj!=null) return singleobj; else { singleobj=new Singleton(); return singleobj; } } public void dosomething() { System.out.println("一个女朋友就够了!!!"); }}
2. 懒汉式(线程安全,支持多线程)

对比:

  • 是否 Lazy 初始化:是
  • 是否多线程安全:是
  • 实现难度:易

描述: 这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步,可以说是一种以时间换空间的方法。

  • 优点:第一次调用才初始化,避免内存浪费。
  • 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率,虽然getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)

实例

package 单例模式;
public class Singleton { private static Singleton singleobj;
private Singleton() { }// 通过private限定,不能通过new建立对象,也不能继承(可以有继承操作,但是无意义)
public static synchronized Singleton getinstance() {//synchronized 才能保证多线程单例 if (singleobj != null) return singleobj; else { singleobj = new Singleton(); return singleobj; } }
public void dosomething() { System.out.println("一个女朋友就够了!!!"); }}
3. 饿汉式(线程安全,支持多线程)

对比:

  • 是否 Lazy 初始化:否
  • 是否多线程安全:是
  • 实现难度:易

描述:这种方式比较常用,但容易产生垃圾对象。即以空间换时间的方式。优点:没有加锁,执行效率会提高。缺点:类加载时就初始化,浪费内存。它基于 classloader 机制避免了多线程的同步问题, 虽然避免了多线程时调用getinstance时的线程问题,但是不能避免的是其他静态方法也能创建singleton对象。

实例

package 单例模式;
public class Singleton { private static Singleton singleobj = new Singleton();
private Singleton() { }// 通过private限定,不能通过new建立对象,也不能继承(可以有继承操作,但是无意义)
public static synchronized Singleton getinstance() { return singleobj; }
public void dosomething() { System.out.println("一个女朋友就够了!!!"); }
}
4.双检锁/双重校验锁 "DCL,即 double-checked locking"(线程安全,支持多线程)

对比:

  • 是否 Lazy 初始化:是
  • 是否多线程安全:是
  • 实现难度:较复杂

描述: 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键。

实例

package 单例模式;
public class Singleton { private volatile static Singleton singleobj;
private Singleton() { }// 通过private限定,不能通过new建立对象,也不能继承(可以有继承操作,但是无意义)
public static synchronized Singleton getinstance() { if (singleobj == null) { synchronized (Singleton.class) { if (singleobj == null) { singleobj = new Singleton(); } } } return singleobj; }
public void dosomething() { System.out.println("一个女朋友就够了!!!"); }
}

其余方法: 登记式/静态内部类,枚举

应用实例

配置文件,如果被系统的很多地方使用,那么不能总是去打开文件吧,那样很占用资源,所以我们可以使用单例模式,返回配置文件对象。

package 单例模式;
import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.util.Properties;
public class Singel_Loder { //饿汉Hunger方式 private static Singel_Loder singleobj = new Singel_Loder();
private Singel_Loder() { }// 通过private限定,不能通过new建立对象,也不能继承(可以有继承操作,但是无意义) private static Properties props = new Properties(); static { try { props.load(new FileInputStream("配置文件.properties")); } catch (FileNotFoundException e) { e.printStackTrace(); System.exit(-1); } catch (IOException e) { System.exit(-1); } }
public static Singel_Loder getinstance() { return singleobj; }
public Properties getpro() { return props; }}
package 单例模式;
import java.util.Properties;
public class SingletonTest {
public static void main(String[] args) { Singel_Loder sloder=Singel_Loder.getinstance(); Properties pro=sloder.getpro(); /** 借助配置文件做一些事情 */ }
}

如果有什么想看的,可以私信我,如果在能力范围内,我会发布相应的博文!感谢大家的阅读!😘你的点赞、收藏、关注是对我最大的鼓励!