什么是单例设计模式?
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
具体实现
需要:
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。
(3)定义一个静态方法返回这个唯一对象。
实现一:立即加载 / “饿汉模式”
立即加载就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故又被称为“饿汉模式”),常见的实现办法就是直接new实例化。
package com.Geeksun.singleton;
/**
* “饿汉模式”的优缺点:
*
* 优点:实现起来简单,没有多线程同步问题。
*
* 缺点:当类Singleton01被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static
* 的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,
* 因此在某些特定条件下会耗费内存。
*/
public class Singleton01 {
private static Singleton01 instance = new Singleton01();
private Singleton01(){}
public static Singleton01 getInstance(){
return instance;
}
}
实现二:延迟加载 / “懒汉模式”
延迟加载就是调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急,故又称为“懒汉模式”),常见的实现方法就是在get方法中进行new实例化。
package com.Geeksun.singleton;
/**
* “懒汉模式”的优缺点:
*
* 优点:1.在多线程情形下,保证了“懒汉模式”的线程安全。
* 2.实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法
* 第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
*
* 缺点:众所周知在多线程情形下,synchronized方法通常效率低,显然这不是最佳的实现方案。
*/
public class Singleton02 {
private static Singleton02 instance;
private Singleton02(){}
public static synchronized Singleton02 getInstance() {
if(instance == null){
instance = new Singleton02();
}
return instance;
}
}
实现三:DCL双检查锁机制(DCL:double checked locking)
package com.Geeksun.singleton;
/**
* “双重检查锁”的优缺点:
*
* 双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。
*
* 双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
*/
public class Singleton03 {
private volatile static Singleton03 instance;//使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。
private Singleton03(){}
public static Singleton03 getInstance() {
if(instance == null){
synchronized (Singleton03.class){
if(instance == null){
instance = new Singleton03();
}
}
}
return instance;
}
}
实现四:静态内部类
package com.Geeksun.singleton;
/**
* 静态内部类
*注解:定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在
* Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。
* 优点:达到了lazy loading的效果,即按需创建实例。
*
*/
public class Singleton04 {
private Singleton04(){
}
private static class SingletonHolder{
private static final Singleton04 instance = new Singleton04();
}
public Singleton04 getInstance(){
return SingletonHolder.instance;
}
}
实现五:枚举类
package com.Geeksun.singleton;
/**
* “枚举类”
* 这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,
* 可谓是很坚强的壁垒啊,书写较为方便。
*
*/
public enum Singleton05 {
INSTANCE;
public void operation(){
}
}
除了枚举类外,单例模式可以被破解
package com.Geeksun.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
public class Client {
public static void main(String[] args) throws Exception {
//通过反射破解单例模式
Singleton01 sc1 = Singleton01.getInstance();
Singleton01 sc2 = Singleton01.getInstance();
Class<Singleton01> clazz = (Class<Singleton01>)Class.forName("com.Geeksun.singleton.Singleton01");
Constructor<Singleton01> c = clazz.getDeclaredConstructor();
c.setAccessible(true);
Singleton01 sc3 = c.newInstance();
Singleton01 sc4 = c.newInstance();
System.out.println(sc1);
System.out.println(sc2);
System.out.println(sc3);
System.out.println(sc4);
//通过反序列化破解单例模式
FileOutputStream fis = new FileOutputStream("D:/test.txt");
ObjectOutputStream oos = new ObjectOutputStream(fis);
oos.writeObject(sc1);
oos.close();
fis.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/test.txt"));
Singleton01 sc5 = (Singleton01)ois.readObject();
System.out.println(sc1);
System.out.println(sc5);
}
}
对于饿汉模式,防止破解的方法是
public class Singleton01 implements Serializable {
private static Singleton01 instance = new Singleton01();
private Singleton01(){
//如果已经创建了对象,则不能调用无参构造函数!
if(instance != null){
throw new RuntimeException();
}
}
public static Singleton01 getInstance(){
return instance;
}
//反序列化时,如果定义了readResolve()则直接返回该方法指定的对象,不需要指定新的对象。
private Object readResolve(){
return instance;
}
}