设计模式2-单例模式

一、概念

创建型模式

单例模式解决某个类频繁创建与销毁。该模式保证其创建的对象在JVM中只有一个实例对象存在,并且提供一个访问该实例的全局访问点。前提是:必须保证私有化构造函数且只能有一个实例对象存在。

单例模式实现过程:

1)将该类的构造函数私有化(目的是禁止其他程序创建该类的对象, 避免反射创建实例, 可以在构造函数中增加判定是否已存在一个实例);

2)在本类中自定义一个对象(已经禁止其他程序创建该类的对象,则必须自己创建一个供程序使用,否则该类无法使用,更不是单例);

3)提供一个可访问类自定义对象的类成员方法(对外提供该对象的访问方式,由于该类不可再次创建对象,则只能通过类调用,所以创建static方法)。

优点:

  • 减少new关键字的使用,降低系统内存的使用频率,同时减轻GC工作;
  • 避免了资源的多重使用。

缺点:

  •  不可继承,没有接口。

二、分类

1、饿汉式

  • 优点:线程安全,没有加锁同步,执行效率高, 不延迟加载。
  • 缺点:当类加载时就初始化,没有懒加载,浪费内存,通过classloader机制避免了多线程的同步问题
 1 /**
 2  * 恶汉单例模式,当类加载时进行初始化,没有懒加载,浪费内存
 3  */
 4 public class HungrySingleton {
 5     private static HungrySingleton instance = new HungrySingleton();
 6     /**
 7      * 构造函数必须私有化
 8      */
 9     private HungrySingleton(){
10 
11     }
12 
13     /**
14      *提供一个静态方法获取实例
15      * @return
16      */
17     public static HungrySingleton getInstance(){
18         return instance;
19     }
20 }
View Code

使用反射的破解与防御:

 1 public class Singleton {
 2 
 3     // 类初始化时立即创建对象
 4     private static final Singleton instance = new Singleton();
 5 
 6     // 私有化构造器
 7     private Singleton() {
 8         // 防御:再次创建时抛出异常
 9         if (instance != null) {
10             throw new RuntimeException();
11         }
12     }
13 
14     public static Singleton getInstance() {
15         return instance;
16     }
17 
18 }
View Code

 调用示例:

import java.lang.reflect.Constructor;

public class Client {

    public static void main(String[] args) throws Exception {
        Singleton singleton1 = Singleton.getInstance();

        Class<Singleton> clazz = Singleton.class;
        Constructor<Singleton> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton singleton2 = constructor.newInstance();
        System.out.println(singleton1 == singleton2);
    }

}
View Code

使用序列化的破解与防御:

 1 import java.io.Serializable;
 2 
 3 public class Singleton implements Serializable {
 4 
 5     private static final long serialVersionUID = -3230831923851678463L;
 6 
 7     // 类初始化时立即创建对象
 8     private static final Singleton instance = new Singleton();
 9 
10     // 私有化构造器
11     private Singleton() {
12     }
13 
14     public static Singleton getInstance() {
15         return instance;
16     }
17 
18     // 防御:反序列化时,直接返回该方法的返回值
19     private Object readResolve() {
20         return instance;
21     }
22 
23 }
View Code

调用示例:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Client {

    public static void main(String[] args) throws Exception {
        Singleton singleton1 = Singleton.getInstance();

        File tempFile = new File("D:/test");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(tempFile));
        oos.writeObject(singleton1);
        oos.close();
        ObjectInputStream ios = new ObjectInputStream(new FileInputStream(tempFile));
        Singleton singleton2 = (Singleton) ios.readObject();
        ios.close();
        System.out.println(singleton1 == singleton2);

    }

}
View Code

2、懒汉式

实现方式一:

  • 优点:实现懒加载,实例化对象是在调用getInstance()后
  • 缺点:没有加锁synchronized,多线程使用下存在问题
 1 /**
 2  * 懒汉式方式一
 3  */
 4 public class LazySingleton {
 5     private static LazySingleton instance = null;
 6     private LazySingleton(){}
 7     public static  LazySingleton getInstance(){
 8         if(instance == null){
 9             instance = new LazySingleton();
10         }
11 
12         return instance;
13     }
14 }
View Code

实现方式二:

  • 改进:增加synchronized关键字,解决多线程问题
  • 不足:synchronized锁住了这个对象,每次调用getInstance()都会对对象上锁,这样大大降低了性能,事实上只有在第一次instance为空时才需要加锁。
/**
 * 懒汉方式二
 */
class LazySingleton2{
    private static  LazySingleton2 instance = null;
    private LazySingleton2(){}
    public static synchronized LazySingleton2 getInstance(){
        if(instance == null){
            instance = new LazySingleton2();
        }

        return instance;
    }
}
View Code

实现方式三(双重检测):

改进:对instance对了判断,只有当instance为空时才对对象进行加锁,提升性能。

不足:无序写入问题,如

a、线程1和2进入getInstance();

b、线程1首先进入synchronized线程同步,线程2等待线程1执行完成;

c、线程1判断instance为空则分配地址内存空间并实例化该对象;

d、线程1执行完成退出;

e、线程2进入synchronized同步,此时instance已被线程1是实例化,instance不为空,则返回线程1创建的instance实例。

由于JVM无序写入问题(指令重排),导致线程2有可能返回instance == null,如下

例如 instance = new Singleton() 可分解为如下伪代码:

memory = allocate(); //1:分配对象的内存空间

ctorInstance(memory); //2:初始化对象

instance = memory; //3:设置instance指向刚分配的内存地址

但是经过指令重排序后会变成这样

1 memory = allocate();   //1:分配对象的内存空间  
2 instance = memory;     //3:设置instance指向刚分配的内存地址  
3                        //注意,此时对象还没有被初始化!  
4 ctorInstance(memory);  //2:初始化对象 

 如:线程A 执行第4步采用先分配内存,然后将instance指向分配的内存,最后初始化,线程B在A线程执行初始化之前执行到第1步,发现非null,直接返回则最终出现了null。

这样就会出现问题,线程A执行了instance = memory(),这一步对线程B是可见,那么,线程B判断if(instance == null)时便会发现instance已经不为空了,便会返回instance,但是,由于instance只是指向了内存地址,并没有真正的初始化,那么线程B将会返回一个未能完全初始化的instance。

在JDK1.5之后,可以使用volatile变量禁止指令重排序

private static volatile LazySingleton2 instance = null;

 1 /**
 2  * 懒汉方式三
 3  */
 4 class LazySingleton3{
 5     private static LazySingleton3 instance = null;
 6     private LazySingleton3(){}
 7     public static LazySingleton3 getInstance(){
 8         if(instance == null){// 1
 9             synchronized (LazySingleton3.class){ //2
10                 if(instance == null){//3
11                     instance = new LazySingleton3(); //4
12                 }
13             }
14         }
15 
16         return instance;
17     }
18 }
View Code

实现方式四(静态内部类):

  • 优点:懒加载策略,线程安全, 执行效率高。利用classloader加载机制实现初始化时只有一个线程,当LazySingleton4被加载时,instance不一定被初始化,因为SingleFactory没有被主动调用
  • 缺点:若在构造行数中抛出异常,将得不到实例
 1 /**
 2  * 懒汉方式四
 3  */
 4 class LazySingleton4{
 5     private LazySingleton4(){
 6 
 7     }
 8 
 9     private static  class SingleFactory{
10         public static LazySingleton4 instance = new LazySingleton4();
11     }
12 
13     public static LazySingleton4 getInstance(){
14         return SingleFactory.instance;
15     }
16 }
View Code
3、枚举实现

优点:线程安全,执行效率高,不能延迟加载
缺点:无法继承(这个其实也谈不上是缺点哈)

这么多种方式实现单例,如何选择呢?
  •   单例对象占用资源少,不需要延时加载:枚举式好于饿汉式;
  •   单例对象占用资源多,需要延时加载:静态内部类好于懒汉式。

 1 public enum Singleton {
 2      
 3     // 枚举本身就是单例
 4     INSTANCE;
 5      
 6      // 添加需要的方法
 7       public void method() {
 8      }
 9     
10 }
View Code
 

三、单例模式在Java中的应用及解读

Runtime是一个典型的例子,看下JDK API对于这个类的解释"每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接,可以通过getRuntime方法获取当前运行时。应用程序不能创建自己的Runtime类实例。",这段话,有两点很重要:

1、每个应用程序都有一个Runtime类实例

2、应用程序不能创建自己的Runtime类实例

只有一个、不能自己创建,是不是典型的单例模式?看一下,Runtime类的写法:

 1 public class Runtime {
 2     private static Runtime currentRuntime = new Runtime();
 3 
 4     /**
 5      * Returns the runtime object associated with the current Java application.
 6      * Most of the methods of class <code>Runtime</code> are instance 
 7      * methods and must be invoked with respect to the current runtime object. 
 8      * 
 9      * @return  the <code>Runtime</code> object associated with the current
10      *          Java application.
11      */
12     public static Runtime getRuntime() { 
13     return currentRuntime;
14     }
15 
16     /** Don't let anyone else instantiate this class */
17     private Runtime() {}
18 
19     ...
20 }

以上就能看出Runtime使用getRuntime()方法并让构造方法私有保证程序中只有一个Runtime实例且Runtime实例不可以被用户创建。

还有以下在Java中的应用:

  • Windows的Task Manager(任务管理器)。
  • Windows的Recycle Bin(回收站)。
  • 项目中,读取配置文件的类,一般只有一个对象,没必要每次创建。
  • 数据库连接池。
  •  Application是单例的典型应用(Servlet编程)。
  • Spring中,每个Bean默认是单例的。
  • 每个Servlet是单例。
  • Spring MVC中,控制器对象是单例的。 

  

原文地址:https://www.cnblogs.com/dudu2mama/p/10566607.html