• 设计模式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中,控制器对象是单例的。 

      

  • 相关阅读:
    (转)运维角度浅谈MySQL数据库优化
    关于MySQL的null值
    MySQL优化——or条件优化
    MySQL优化原理
    Xcode Archive打包失败问题
    ionic3 对android包进行签名
    ios 审核未通过 相机相册权限问题
    js计算两个日期相差天数
    截取URL链接中字段的方法
    ionic3 自定义组件 滑动选择器 ion-multi-picker
  • 原文地址:https://www.cnblogs.com/dudu2mama/p/10566607.html
Copyright © 2020-2023  润新知