所谓单例模式,也就是说在整个系统中,对于设计成单例模式的类,在创建对象时都只返回一个实例,对于整一个类,系统中只会生成一个对象。比如说在我们的Spring中,创建Bean的实例默认都是单例模式;
为了能够说清楚单例模式,我们通过一个例子来向大家描述单例模式如何实现以及单例模式的相关场景。
比如说在自然环境中,对于任何物体来讲,我们的地球(Earth)都只有一个,所以无论是任何人,在他需要获取一个地球信息的时候,他们都是获取到的一个统一的地球对象。比如说现在我们有一个描述地球的Earth类如下:
public class Earth {
/**
* 地球的称谓
*/
private String name;
/**
* 地球的名称
*/
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
一般情况下我们需要获取一个Earth类的对象时,我们都会按照如下的方式去操作:
public class MainTest {
public static void main(String[] args) {
Earth earth01 = new Earth();
earth01.setAge(1000000);
earth01.setName("地球");
Earth earth02 = new Earth();
earth02.setAge(1000000);
earth02.setName("地球01");
System.out.println("地球名字:"+earth01.getName());
System.out.println("地球名字:"+earth02.getName());
}
}
上面的代码非常简单,输出的结果大家也一定知道,其结果如下
地球名字:地球
地球名字:地球01
同一个世界,同一个梦想,所以地球我们也只有同一个,所以对于我们的地球(Earth )这个类来说,我们只要有一个人创建,那么其他人不用创建也可以获得相同的对象,或者说对于Earth 这个类的对象的属性来讲,它的影响应该是全局的,它的输出结果一定是需要一致的。怎么解决这个问题呢,那么就需要我们本文需要描述的“单例模式”来解决;
1、单例模式之——饿汉式
如果说我们要让一个类他在整个应用程序上下文中它只有一个实例,那么毋庸置疑我们就必须禁止在任何类中通过new关键字来创建类的实例对象,所以我们首先要做的就是将类的构造函数私有化,通过构造函数私有化来杜绝在任何其他的类中通过new关键字来创建单例类的实例;
构造函数私有化之后,我们需要暴露一个方法给使用类,让它们能够通过这个方法获取单例类的单例。
首先我们讲单例模式中的饿汉式单例模式,所谓饿汉饿者,饥不择食;但凡有食,必急食之——即在加载类的时候就会创建类的单例,并保存在类中。
饿汉式单例模式的代码如下(我们还是以Earth类为例)
public class Earth1 {
private final static Earth1 EARTH1 = new Earth1("地球");
private Earth1(String name) {
this.name=name;
}
/**
* 获取实例的方法
* @return
*/
public static Earth1 getEarth() {
return EARTH1;
}
/**
* 地球的称谓
*/
private String name;
/**
* 地球的名称
*/
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
}
2、单例模式之——懒汉式
所谓懒汉式,顾名思义,就是不做事,这里也是同义,懒汉式就是不在系统加载时就创建类的单例,而是在第一次使用实例的时候再创建。具体实例如下:
public class Earth2 {
private static Earth2 earth = null;
private Earth2(String name) {
this.name=name;
}
/**
* 获取实例的方法
* @return
*/
public static Earth2 getEarth() {
if(earth == null) {
earth=new Earth2("地球");
}
return earth;
}
//... ...
}
3、线程安全的单例模式(双重检查机制)
对于我们的饿汉式单例模式来说,因为我们在类的加载时就已经创建了单例,所以饿汉式单例模式是线程安全的,但是对于我们的懒汉式来讲,创建单例时他就不是线程安全的了。那么如何设计出一个线程安全的懒汉式单例模式,那么大家可能都知道需要加锁。
首先可能想到的就是在获取实例的方法上加上线程同步关键字来进行同步来保证线程安全;
public static synchronized Earth2 getEarth() {
if(earth == null) {
earth=new Earth2("地球");
}
return earth;
}
这样做基本上能解决线程同步的问题,但是这样做有一个缺陷,那就是如果我们在执行earth=new Earth2("地球");
这段代码之前,我们有很多的工作要做,而且这些做这些工作很耗时,但是在我们的整个方法体中,我们需要同步的仅仅只是如下的代码块:
if(earth == null) {
earth=new Earth2("地球");
}
所以整个方法都做线程同步是不可取的,因此我们就引入了我们的双重检查机制,所谓双重检查就是在方法体中,执行同步创建单例对象之前检查一次,如果对象已经创建,则直接返回;如果对象尚未创建,则进入同步代码块,进入同步代码块之后再进行一次检查,如果单例对象还是为null,那么就实例化对象并返回,具体代码如下:
public class Earth3 {
private static Earth3 earth = null;
private Earth3(String name) {
this.name=name;
}
/**
* 获取实例的方法
* @return
public static Earth3 getEarth() {
if(earth == null) {
synchronized(Earth3.class) {
if(earth == null) {
earth=new Earth3("地球");
}
}
}
return earth;
}
//... ...
}
小结
单例模式作为一种目标明确、 结构简单、 理解容易的设计模式, 在软件开发中使用频率相当高, 在很多应用软件和框架中都得以广泛应用。
1. 主要优点
单例模式的主要优点如下:
单例模式提供了对唯一实例的受控访问。 因为单例类封装了它的唯一实例, 所以它可以严格控制客户怎样以及何时访问它。
由于在系统内存中只存在一个对象, 因此可以节约系统资源, 对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
2. 主要缺点
由于单例模式中没有抽象层, 因此单例类的扩展有很大的困难。
单例类的职责过重, 在一定程度上违背了“单一职责原则”。 因为单例类既充当了工厂角色, 提供了工厂方法, 同时又充当了产品角色, 包含一些业务方法, 将产品的创建和产品的本身的功能融合到一起。
现在很多面向对象语言(如Java、 C#)的运行环境都提供了自动垃圾回收的技术, 因此, 如果实例化的共享对象长时间不被利用, 系统会认为它是垃圾, 会自动销毁并回收资源, 下次利用时又将重新实例化, 这将导致共享的单例对象状态的丢失。
3. 适用场景
系统只需要一个实例对象, 如系统要求提供一个唯一的序列号生成器或资源管理器, 或者需要考虑资源消耗太大而只允许创建一个对象。
客户调用类的单个实例只允许使用一个公共访问点, 除了该公共访问点, 不能通过其他途径访问该实例。