• Java单例模式


    再孬再好,就你一个

    单例模式

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

    注意:

    • 单例类限制类的实例个数,保证类的实例在JVM的世界里只有一个类的实例对象。

    • 单例类必须自己创建自己的唯一实例。
    • 单例类必须提供一个全局性的公共访问方式获取单例类的实例对象

    • 单例类必须给所有其他对象提供这一实例。

    介绍

    意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    主要解决:一个全局使用的类频繁地创建与销毁。

    何时使用:当您想控制实例数目,节省系统资源的时候。

    如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

    关键代码:构造函数是私有的。

    应用场景: java.lang.Runtime类、日志、驱动器、缓存、线程池等l

    实现

    我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。

    SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo,我们的演示类使用 SingleObject 类来获取 SingleObject 对象。

    步骤 1

    创建一个 Singleton 类,SingleObject.java

    public class SingleObject {
    	 //创建 SingleObject 的一个对象
    	   private static SingleObject instance = new SingleObject(0);
    	 
    	   //让构造函数为 private,这样该类就不会被实例化
    	   private SingleObject(int i){
    		      System.out.println("我是一个单例"+i);
    	   }
    	 
    	   //获取唯一可用的对象
    	   public static SingleObject getSingletonInstance(){
    	      return instance;
    	   }
    	 
    	   public void showMessage(String msg){
    	      System.out.println("我是一个单例的方法调用,"+msg);
    	   }
    }

    步骤 2

    从 singleton 类获取唯一的对象,测试类SingletonPatternDemo.java

    /**
     * 
     * @author dgm
     * @describe "测试单例模式"
     * @date 2020年5月13日
     */
    public class SingletonPatternDemo {
    
    	 public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
    
    		// 不合法的构造函数
    		// 编译时错误:构造函数 SingleObject() 是不可见的
    		// SingleObject object = new SingleObject();
         
    		// 获取唯一可用的对象
    		SingleObject object = SingleObject.getSingletonInstance();
    
    		// 显示消息
    		object.showMessage("正常流程");
    	}
    
    }

    输出效果:

     

    不过java实现单例模式有好几种方式,简单绘图一张https://www.processon.com/view/link/5ebb9c087d9c08156c3be39a,如下:

    我就一一列举:

    1.  饿汉式初始化 Eager initialization

    在饿汉初始化模式下,单例类的实例是在类加载的时候完成了创建,这是最简单的创建单例的方式,但它有个缺陷就是客户端还没使用已创建好的单例实例。下面就是实现了静态初始化的单例类

    /**
     * 
     * @author dgm
     * @describe "饿汉式初始化单例"
     */
    public class EagerInitializedSingleton {
        
        private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
        
        //注意是私有构造器,防止客户端使用
        private EagerInitializedSingleton(){}
    
        //全局访问方式获取单实例
        public static EagerInitializedSingleton getSingletonInstance(){
            return instance;
        }
        
        public void showMessage(String msg){
    	      System.out.println("我是一个饿汉式初始化单例的方法调用,"+msg);
        }
    }

    2. 静态块初始化 Static block initialization 

     静态块初始化的实现单例和饿汉式初始化类似,但它在创建类实例的时候提供了异常处理

    /**
     * 
     * @author dgm
     * @describe "静态代码块初始化"
     */
    public class StaticBlockSingleton {
    
        private static StaticBlockSingleton instance;
        
        private StaticBlockSingleton(){}
        
        //在静态代码块初始化进行异常处理
        static{
            try{
                instance = new StaticBlockSingleton();
            }catch(Exception e){
                throw new RuntimeException("创建单实例时发生了异常");
            }
        }
        
        public static StaticBlockSingleton getSingletonInstance(){
            return instance;
        }
        
        public void showMessage(String msg){
    	      System.out.println("我是一个静态代码块初始化单例的方法调用,"+msg);
        }
    }

    3. 延迟初始化 Lazy Initialization

    延迟初始化意味着创建单例的是时机不是在类加载过程了,而是在全局访问方法里实现单例模式的实例创建,就是需要时再创建

    /**
     * 
     * @author dgm
     * @describe "延迟初始化实例"
     */
    public class LazyInitializedSingleton {
        private static LazyInitializedSingleton instance;
        
        private LazyInitializedSingleton(){}
        
        public static LazyInitializedSingleton getSingletonInstance(){
            if(instance == null){
                instance = new LazyInitializedSingleton();
            }
            return instance;
        }
        public void showMessage(String msg){
    	      System.out.println("我是一个延迟初始化单例的方法调用,"+msg);
        }
    }
    

    此种实现方式在单线程环境下表现很好,但是现实中往往是多线程环境,故有了下面的其他实现方式

    4.  线程安全型单例Thread Safe Singleton 

    有一种简单创建线程安全的单例,就是给全局访问方法加上关键字synchronized (锁的一种),它可以确保只有一个线程执行获取单例的全局访问方法。

    /**
     * 
     * @author dgm
     * @describe "线程安全的单例"
     */
    public class ThreadSafeSingleton {
    
    private static ThreadSafeSingleton instance;
        
        private ThreadSafeSingleton(){}
        
        public static synchronized  ThreadSafeSingleton getSingletonInstance(){
            if(instance == null){
                instance = new ThreadSafeSingleton();
            }
            return instance;
        }
        public void showMessage(String msg){
    	      System.out.println("我是一个线程安全的单例的方法调用,"+msg);
        }  
    }

     Above implementation works fine and provides thread-safety but it reduces the performance because of the cost associated with the synchronized method, although we need it only for the first few threads who might create the separate instances (Read: Java Synchronization). To avoid this extra overhead every time, double checked locking principle is used. In this approach, the synchronized block is used inside the if condition with an additional check to ensure that only one instance of a singleton class is created.

    该实现确实实现了线程安全的单例,可同步方法对性能有影响,可改造为双重检测锁

    /**
     * 
     * @author dgm
     * @describe "线程安全双重检测的单例"
     */
    public class ThreadSafeDoubleCheckSingleton {
    
    private static ThreadSafeDoubleCheckSingleton instance;
        
        private ThreadSafeDoubleCheckSingleton(){}
        
        public static   ThreadSafeDoubleCheckSingleton getSingletonInstance(){
        	 if(instance == null){
        	        synchronized (ThreadSafeDoubleCheckSingleton.class) {
        	            if(instance == null){
        	                instance = new ThreadSafeDoubleCheckSingleton();
        	            }
        	        }
        	    }
        	    return instance;
        }
        public void showMessage(String msg){
    	      System.out.println("我是一个线程安全双重检测单例的方法调用,"+msg);
        }  
    }

    5. 内部静态辅助类  Inner Static Helper Class 

     借助内部静态类创建单例

    /**
     * 
     * @author dgm
     * @describe "内部静态辅助类"
     */
    public class InnerHelperSingleton {
    
        private InnerHelperSingleton(){}
        
        private static class SingletonHelper{
            private static final InnerHelperSingleton INSTANCE = new InnerHelperSingleton();
        }
        
        public static InnerHelperSingleton getSingletonInstance(){
            return SingletonHelper.INSTANCE;
        }
        
        public void showMessage(String msg){
    	      System.out.println("我是一个内部静态辅助类的单例的方法调用,"+msg);
        }  
    }
    

    特别注意,当单例类被加载的时候,内部辅助类还没有加载到内存,当有客户端调用公共访问getSingletonInstance方法时内部类才被加载,然后创建单例对象。

    一句话:私有内部静态类负责创建单例类的实例对象,且不需要同步机制

    7. 序列化单例 Serialization and Singleton

    有时在分布式系统中,为了在文件系统中存放单实例状态,我们需要在单例类中实现序列化接口(Serializable interface),下面是一个实现了序列化接口的单实例类:

    import java.io.Serializable;
    
    /**
     * 
     * @author dgm
     * @describe "序列化单例"
     */
    public class SerializedSingleton implements Serializable{
    
        private static final long serialVersionUID = -7604766932017737115L;
    
        private SerializedSingleton(){}
        
        private static class SingletonHelper{
            private static final SerializedSingleton instance = new SerializedSingleton();
        }
        
        public static SerializedSingleton getSingletonInstance(){
            return SingletonHelper.instance;
        }
        
        public void showMessage(String msg){
    	      System.out.println("我是一个序列化单例的方法调用,"+msg);
       }   
    }

    .序列化的单实例进行反序列化时会出现问题,创建了一个新的类实例对象,如下代码:

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutput;
    import java.io.ObjectOutputStream;
    
    /**
     * 
     * @author dgm
     * @describe "序列化单例测试"
     */
    public class SingletonSerializedTest {
    
        public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
            SerializedSingleton instanceOne = SerializedSingleton.getSingletonInstance();
            ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                    "SerializedSingleton.ser"));
            out.writeObject(instanceOne);
            out.close();
            
            //反序列化对象
            ObjectInput in = new ObjectInputStream(new FileInputStream(
                    "SerializedSingleton.ser"));
            SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
            in.close();
            System.err.println("相等否: "+(instanceOne==instanceTwo));
            System.out.println("instanceOne hashCode="+instanceOne.hashCode());
            System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
        }
    }

    输出结果:

     

     可以通过在单实例类的定义中加上

    private Object readResolve() throws ObjectStreamException{
        	  //instead of the object we're on,
        	  //return the class valiable INSTANCE
        	  return SingletonHelper.instance;
        }

     注意,反序列化会判断是否有readResolve方法

    readResolveMethod = getInheritableMethod(
                            cl, "readResolve", null, Object.class);
    
    
    /**
         * Returns true if represented class is serializable or externalizable and
         * defines a conformant readResolve method.  Otherwise, returns false.
         */
        boolean hasReadResolveMethod() {
            return (readResolveMethod != null);
        }

     这样当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例模式规则也就得到了保证.

     

    此时非常好,JVM世界里只有一个单实例对象

     8. 枚举单例 Enum Singleton

    用枚举的方式实现单例模式也不失为一种好方法,它可以保证任何一个枚举值只被实例化一次

    /**
     * 
     * @author dgm
     * @describe "枚举单例"
     */
    public enum  EnumSingleton {
    	    INSTANCE,
    	    OTHER_INSTANCE;
    	    public void showMessage(String msg){
    		      System.out.println("我是一个枚举型单例的方法调用,"+msg);
    	    }  
    }

    测试枚举型单例代码:

    public class EnumSingletonTest {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
    		EnumSingleton otherEnumSingleton = EnumSingleton.INSTANCE;
    		EnumSingleton secondEnumSingleton = EnumSingleton.OTHER_INSTANCE;
    
    		System.err.println("枚举型单例对象相等否: "+(enumSingleton==otherEnumSingleton));
    	    System.out.println("enumSingleton hashCode="+enumSingleton.hashCode());
    	    System.out.println("otherEnumSingleton hashCode="+otherEnumSingleton.hashCode());
    		System.err.println("枚举型单例对象相等否: "+(enumSingleton==secondEnumSingleton));
    	}
    }

     输出结果

    9. 使用反射 ,魔方

    可以使用反射,注意,它和没有使用readResolve()方法的序列化单例模式类似,会破坏单例模式,JVM的世界里会有多个实例对象

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * 
     * @author dgm
     * @describe "反射单例"
     */
    public class ReflectionSingletonTest {
    	 public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
    
    		// 获取唯一可用的对象
    		SingleObject object = SingleObject.getSingletonInstance();
    		// 显示消息
    		object.showMessage("正常流程");
    
    		// 获取Class对象
    		Class<?> singltonClass = null;
    		SingleObject otherObject = null;
    		try {
    			singltonClass = Class.forName("code.singleton.SingleObject", false,
    					SingletonPatternDemo.class.getClassLoader());
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    
    		// 已经确定有Class对象,实际情况需要做判断类加载是否成功,获取所有构造器
    		Constructor<?>[] allConstructors = singltonClass
    				.getDeclaredConstructors();// getConstructors();
    		System.out.println("所有构造器个数:" + allConstructors.length);
    		for (Constructor constructor : allConstructors) {
    			System.out.println("构造器:" + constructor);
    			if (!constructor.isAccessible()) {
    				System.out.println("我是一个私有构造器");
    				constructor.setAccessible(true);
    			}
    			// 用获取到的构造器实例化对象
    			otherObject = (SingleObject) constructor.newInstance(1);
    
    			// 通过“参数化”获取类里的方法
    			Method method = singltonClass
    					.getMethod("showMessage", String.class);
    			// 执行方法
    			method.invoke(otherObject, "不正常流程");
    		}
    		 System.err.println(object == otherObject);
    	     System.out.println("object hashCode="+object.hashCode());
    	     System.out.println("reflection Object hashCode="+otherObject.hashCode());
    		}
    }
    

    执行输出结果(好可怕破坏单例模式)

    发现两个实例并不相等,反射很强大,被广泛用于像Spring、Hibernate、Mybatis等框架中,普通开发禁止使用。加上我的反射一文,从此以后,你可以光明正大的说,私有属性、私有构造器、私有方法可以访问兼修改了

    小结: 单例模式实现多种多样,实际场景自行把控选择。代码已进入github: https://github.com/dongguangming/design-pattern/tree/master/src/code/singleton

    参考:

    0. 单例模式   https://baike.baidu.com/item/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/5946627

    1. Java Singleton Design Pattern Best Practices with Examples  https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples

    2. 要翻墙 https://javarevisited.blogspot.com/2012/12/how-to-create-thread-safe-singleton-in-java-example.html

    3. double checked locking (同样要翻墙)http://jeremymanson.blogspot.com/2008/05/double-checked-locking.html

    4. The "Double-Checked Locking is Broken" Declaration?
    https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

  • 相关阅读:
    [转]C++中const、volatile、mutable的用法
    [转]pugixml使用教程
    [转]xml解析工具的效率比较QDomDocument、TinyXml-2、RapidXml、PugiXml
    [原][spark]帧序列的纹理UV索引,修改spark源码,改变纹理索引方式,支持常规帧序列
    [原][unreal][UE][spark]分析unreal engine 虚幻引擎的粒子编辑器:Cascade
    [转][C++]佛祖保佑,永无bug。C++ BUG解决方案
    [转]VS中的路径宏 OutDir、ProjectDir、SolutionDir各种路径含义
    [转]jsbsim基础概念
    [原][粒子特效][spark]调节器modifier
    项目管理(八)- 控制项目的范围
  • 原文地址:https://www.cnblogs.com/dongguangming/p/12886186.html
Copyright © 2020-2023  润新知