设计模式之单例模式

Posted blue星空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之单例模式相关的知识,希望对你有一定的参考价值。

0. 单例模式准备

    0.1 单例模式的作用

       0.1.1 一个类只有一个实例

       0.1.2 该实例能够通过一个全局的方法获取到

    0.2 单例模式适用场景:

      0.2.1 多线程之间操作一个对象【共享一个资源】

      0.2.2 性能优化,减少创建对象的消耗【创建时间、对象所占的空间】

   0.3 单例模式的特点

       0.3.1 具有私有的构造器一个静态变量一个静态方法、[一个静态内部类]

 

1 饿汉模式

      1.1本质: 在类加载的时候就创建实例,需要获取实例时直接返回已创建的实例

      1.2 优点:线程安全

      1.3 缺点: 类加载的时候就创建实例,浪费空间

      1.4 代码示例:

package com.blueStarWei.pattern;

public class HungrySingleton {

    //create instance when loading class
    private static HungrySingleton singleton = new HungrySingleton();

//将构造器私有化,防止外界通过该构造器创造多个实例 private HungrySingleton(){}

public static HungrySingleton getInstance(){ return singleton; } }

 

【为了减省空间的浪费,因此人们想到了在需要获取实例的时候创建实例,因此产生了懒汉模式】

 

2. 懒汉模式

  2.1 本质: 当需要获取实例的时候,先进行判断,如果实例不存在,就创建实例,否则直接返回已存在的实例【避免了在加载类的同时直接创建实例】

  2.2 懒汉模式1.0版本

package com.blueStarWei.pattern;

public class LazySingleton {

    private static LazySingleton singleton;

    private LazySingleton(){}
public static LazySingleton getInstance(){ if(singleton == null){ singleton = new LazySingleton(); } return singleton; } }

    2.2.1 优点:节省了空间

    2.2.2 缺点: 线程不安全【如果多个线程同时执行if判断,因为多个线程获取的singleton1都是空,所以会创建多个实例】

 

【为了避免懒汉模式的线程不安全问题,人们想到了对getInstance()添加synchronized修饰符,于是产生了懒汉模式1.1版本】

 

  2.3 懒汉模式1.1版本

package com.blueStarWei.pattern;

public class LazySingleton1 {

    private static LazySingleton1 singleton;

private LazySingleton1(){}
public static synchronized LazySingleton1 getInstance(){ if(singleton == null){ singleton = new LazySingleton1(); } return singleton; } }

    2.3.1 优点: 节省空间、线程安全

    2.3.2 缺点: 性能不好【synchronized会造成线程阻塞】

 

【为了避免线程阻塞的出现,人们又想到了双重校验锁,因此产生了懒汉模式1.2版本】

 

  2.4 懒汉模式1.2版本【双重检查枷锁方式/double-check】

package com.blueStarWei.pattern;

public class LazySingleton2 {

//使用volatile阻止命令重排,保证多线程正确处理singleton变量
private volatile static LazySingleton2 singleton;

private LazySingleton2(){}
public static LazySingleton2 getInstance(){ if(singleton == null){
//该部分只会被执行一次
synchronized (LazySingleton1.class) { if(singleton == null){ singleton = new LazySingleton2(); } } } return singleton; } }

    2.4.1 优点: 节省空间,线程安全,性能相对有优化【只是优化了已经存在实例的情况,即:当实例已经被创建,再调用getInstance()的时候就不会经过synchronized修饰符】

    2.4.2 缺点:使用了synchronized修饰符,线程仍然会有阻塞情况的发生。

 

【为了避免使用synchronized修饰符,人们想到了静态内部类(因为一个类被加载,当且仅当该类的某个静态成员被调用),于是懒汉模式2.0时代到来】

 

  2.5 懒汉模式2.0版本【推荐版本

package com.blueStarWei.pattern;
/**
 * 因为对象实例化是在内部类加载时构建,因此是线程安全的【毕竟类只加载一次嘛】
 * @author HuWei
 *
 */
public class LazySingleton3 {

private LazySingleton3(){}
//当getInstance()方法第一次被调用时,内部类被加载 static class SingletonHolder { private static final LazySingleton3 singleton3 = new LazySingleton3(); } //没有使用synchronized修饰符,避免了性能损耗 public static LazySingleton3 getInstance() { return SingletonHolder.singleton3; } }

     2.5.1 优点: 节省空间、线程安全、避免性能的额外损耗

     2.5.2 缺点: 在反射作用下,单例模式会被破坏

 

【为了防止反射破坏单例模式,所以产生了单例模式3.0版本】

 

  2.6 单例模式3.0版本

     2.6.1 版本代码

package com.blueStarWei.pattern;

public class LazySingleton4 {
    
    /* -------添加部分 start-------- 
     * 作用:第二次调用构造函数(即:创建第二个实例)时抛出异常
     * */
    private static boolean initialized = false;
    
    private LazySingleton4() {
        synchronized (LazySingleton4.class) {
            if(initialized == false){
                initialized = !initialized;
            }else{
                throw new RuntimeException("Singleton has been broken!");
            }
        }
    }
    /* -------添加部分 end-------- */
    
    //当getInstance()方法第一次被调用时,内部类被加载
        static class SingletonHolder {
            private static final LazySingleton4 singleton4 = new LazySingleton4();
        }
        
        //没有使用synchronized修饰符,避免了性能损耗
        public static LazySingleton4 getInstance() {
            return SingletonHolder.singleton4;
        }
}

        2.6.1.1  版本Demo

package com.blueStarWei.pattern;

import java.lang.reflect.Constructor;
/**
 * 通过反射调用时会报错
 * Caused by: java.lang.RuntimeException: Singleton has been broken!
 * @author HuWei
 *
 */
public class LazySingleton4Demo {

    public static void main(String[] args) {
        //通过getInstance()创建实例
        LazySingleton4 sington = LazySingleton4.getInstance();
        
        //通过反射创建实例
        LazySingleton4 sington4 = null;
        try {
            Class<LazySingleton4> clazz = LazySingleton4.class;
Constructor
<LazySingleton4> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); sington4
= constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("getInstance() : "+sington); System.out.println("invoke : "+sington4); } }

         2.6.1.2 Demo日志

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at com.blueStarWei.pattern.LazySingleton4Demo.main(LazySingleton4Demo.java:21)
Caused by: java.lang.RuntimeException: Singleton has been broken!
    at com.blueStarWei.pattern.LazySingleton4.<init>(LazySingleton4.java:19)
    ... 5 more
getInstance() : com.blueStarWei.pattern.LazySingleton4@7852e922
getInstance() : com.blueStarWei.pattern.LazySingleton4@7852e922
invoke : null

 

    2.6.2 反射破环单例模式示例

package com.blueStarWei.pattern;

import java.lang.reflect.Constructor;
/**
 * 不难发现,通过getInstance()和反射创建的实例的内存地址不一样,即不是同一个实例对象
 * @author HuWei
 *
 */
public class LazySingleton3Demo {

    public static void main(String[] args) {
        //通过getInstance()创建实例
        LazySingleton3 sington = LazySingleton3.getInstance();
        LazySingleton3 sington2 = LazySingleton3.getInstance();
        
        //通过反射创建实例
        LazySingleton3 sington3 = null;
        try {
            Class<LazySingleton3> clazz = LazySingleton3.class;
            Constructor<LazySingleton3> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); sington3
= constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println("getInstance() : "+sington); //getInstance() : com.blueStarWei.pattern.LazySingleton3@15db9742 System.out.println("getInstance() : "+sington2); //getInstance() : com.blueStarWei.pattern.LazySingleton3@15db9742 System.out.println("invoke : "+sington3); //invoke : com.blueStarWei.pattern.LazySingleton3@6d06d69c } }

 

 【在分布式系统中,单例类有时需要实现Serializable接口。以上版本通过序列和反序列化产生的实例将会不是同个实例对象,所以产生了懒汉模式4.0版本】

 

  2.7 懒汉模式4.0版本

    2.7.1 Demo代码

package com.blueStarWei.pattern;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class LazySingleton4Demo2 {

    public static void main(String[] args) {
        
        LazySingleton4 singleton = LazySingleton4.getInstance();
        try {
            ObjectOutput out = new ObjectOutputStream(new FileOutputStream("fileName.txt"));
            out.writeObject(singleton);
            out.close();
            //deserializable from file to object
            ObjectInput in = new ObjectInputStream(new FileInputStream("fileName.txt"));
            LazySingleton4 singleton2 = (LazySingleton4)in.readObject();
            in.close();
            
            System.out.println("Serializable : "+singleton);
                        
            System.out.println("Deserializable : "+singleton2);
            
        } catch (Exception e) {
            e.printStackTrace();
        } 
        
    }
}

 

  2.7.2 反序列返回不同的对象示例

public class LazySingleton4 implements Serializable

        2.7.2.1 Demo日志

Serializable : com.blueStarWei.pattern.LazySingleton4@33909752
Deserializable : com.blueStarWei.pattern.LazySingleton4@776ec8df

 

  2.7.3 版本代码

package com.blueStarWei.pattern;

import java.io.Serializable;

public class LazySingleton4 implements Serializable{
    
    private static final long serialVersionUID = 1L;
    
    private static boolean initialized = false;

private LazySingleton4() { synchronized (LazySingleton4.class) { if(initialized == false){ initialized = !initialized; }else{ throw new RuntimeException("Singleton has been broken!"); } } } static class SingletonHolder { private static final LazySingleton4 singleton4 = new LazySingleton4(); } public static LazySingleton4 getInstance() { return SingletonHolder.singleton4; } /* ----添加部分 start---- */ /** * 反序列化的结果仍然通过getInstance()获取 */ private Object readResolve(){ return getInstance(); } /* ----添加部分 end---- */ }

        2.7.3.1 Demo日志

Serializable : com.blueStarWei.pattern.LazySingleton4@33909752
Deserializable : com.blueStarWei.pattern.LazySingleton4@33909752

 

3 其它问题

  3.1 如果存在多个类加载器,可能会产生多个单例并存的现象。

       3.1.1 原因: 每个类加载器都定义了一个命名空间,如果存在多个类加载器,不同的类加载器可能会加载同一个类,所以同一类会被加载多次。

       3.1.2 解决方法: 自行指定类加载器,并指定同一个类加载加载器。

  3.2 单例类不能被继承

      3.2.1 原因: 继承单例类必须将单例类构造器的访问修饰符改成public/protected,这样会导致其他类也可以实例化该单例类,破坏了单例模式

      3.2.2 解决方法: 不继承单例类

  3.3 为何全局变量会比单例模式差?

     3.3.1 全局变量基本上就是对对象的静态引用,全局变量虽然能提供全局访问,但是不能保证只有一个实例

 

4 总结

   实际项目中,一般从懒汉模式2.0、3.0、4.0版本中选择一种即可

 

         更多内容,请访问:http://www.cnblogs.com/BlueStarWei/

以上是关于设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之单例模式

Java设计模式之单例模式

设计模式之单例模式以及简单代码实现

设计模式之单例设计模式

设计模式之单例模式

设计模式之单例模式