• java单例模式


    单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    使用场景:在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反映”,可以使用单例模式。例如:在计算机系统中,线程池、缓存、日志对象、web计数器等 常被设计成单例。这些应用都或多或少具有资源管理器的功能。

    优点:

    (1)单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化, 单例模式的优势就非常明显。

    (2)单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象, 然后用永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制)。

    (3)单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

    (4)单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

    缺点:

    (1)单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。当然,在特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断。

    (2)单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。

    (3)单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

    (摘自《设计模式之禅》)

    单例模式一般分为懒汉式和饿汉式两种:

    区别:

    饿汉式:在类加时就创建对象,线程是安全的,但是没有延迟加载,如果没有使用就是浪费内存。

    懒汉式:在类加载的时候不创建,而是在实际用到时创建,线程不安全(故需加上synchronized),但具有延迟加载的特性。

    实现代码如下:

    饿汉式:

    public class SingletonDemo1 {
        
        //类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
        private static SingletonDemo1 instance = new SingletonDemo1();  
        
        private SingletonDemo1(){
        }
        
        //方法没有同步,调用效率高!
        public static SingletonDemo1  getInstance(){
            return instance;
        } 
    }

    懒汉式:

    public class SingletonDemo2 {
        
        //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
        private static SingletonDemo2 instance;  
        
        private SingletonDemo2(){ //私有化构造器
        }
        
        //方法同步,调用效率低!
        public static  synchronized SingletonDemo2  getInstance(){
            if(instance==null){
                instance = new SingletonDemo2();
            }
            return instance;
        }
        
    }

    怎么能同时具有以上两种模式的共同特点呢?

    常用的方式有两种:(1)双重检查锁实现单例模式 (2)静态内部类实现单例模式

    双重检查锁实现单例模式由于JVM编译器的原因,有的时候会出现问题(不建议使用),代码如下,仅做参考:

    public class SingletonDemo3 { 
    
      private static SingletonDemo3 instance = null; 
    
      public static SingletonDemo3 getInstance() { 
        if (instance == null) { 
          SingletonDemo3 sc; 
          synchronized (SingletonDemo3.class) { 
            sc = instance; 
            if (sc == null) { 
              synchronized (SingletonDemo3.class) { 
                if(sc == null) { 
                  sc = new SingletonDemo3(); 
                } 
              } 
              instance = sc; 
            } 
          } 
        } 
        return instance; 
      } 
    
      private SingletonDemo3() { 
    
      }    
    }

    静态内部类实现单例模式(建议使用这种方式),代码如下:

    /**
     * 这种方式:线程安全,调用效率高,并且实现了延时加载!
     */
    public class SingletonDemo4 {
        
        private static class SingletonClassInstance {
            private static final SingletonDemo4 instance = new SingletonDemo4();
        }
        
        private SingletonDemo4(){
        }
        
        //方法没有同步,调用效率高!
        public static SingletonDemo4  getInstance(){
            return SingletonClassInstance.instance;
        }
        
    }

    还有一种是使用枚举的方式实现单例模式(我没有实际用过),这里代码给大家参考:

    /**
     * 枚举式实现单例模式(没有延时加载)
     */
    public enum SingletonDemo5 {
        
        //这个枚举元素,本身就是单例对象!
        INSTANCE;
        
        //添加自己需要的操作!
        public void singletonOperation(){
        }
            
    }

    从严格的意义上来说把构造器私有后,并不是就不能再创建它的实例了,还可以通过其他的方式进行实例化。这里就和java中创建对象的几种方式相关。

    在java中有四种创建对象的方式:

    (1)通过构造器直接new

    (2)可以通过clone方法创建

    (3)通过反射的方式进行创建

    (4)通过反序列化的方式创建

    这样,虽然私有了构造器,但是还是可以通过反射和反序列化的方式创建对象。

    为了防止通过反射的方式直接创建对象破坏单例模式的结构,可以在构造器中添加判断抛出异常,如下:

    private SingletonDemo6(){ //私有化构造器
        if(instance!=null){
            throw new RuntimeException();
        }
    }

    为了防止通过反序列化的方式创建对象破坏单例模式的结构,可以在复写readResolve个方法,这样在反序列化的时候直接调用这个方法,如下:

    //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
    private Object readResolve() throws ObjectStreamException {
        return instance;
    }

    这样,基本就保证了单例模式的可靠行了。

    附上完整的防止反射和反序列化创建对象的代码:

    import java.io.ObjectStreamException;
    import java.io.Serializable;
    
    /**
     * 懒汉式单例模式(如何防止反射和反序列化漏洞)
     */
    public class SingletonDemo6 implements Serializable {
        //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
        private static SingletonDemo6 instance;  
        
        private SingletonDemo6(){ //私有化构造器
            if(instance!=null){
                throw new RuntimeException();
            }
        }
        
        //方法同步,调用效率低!
        public static  synchronized SingletonDemo6  getInstance(){
            if(instance==null){
                instance = new SingletonDemo6();
            }
            return instance;
        }
        
        //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
        private Object readResolve() throws ObjectStreamException {
            return instance;
        }
        
    }

    扩展,如果一个类可以产生多个对象,对象的数量不受限制,则是非常容易实现的,直接使用new关键字就可以了,如果只需要一个对象,使用单例模式就可以了,但是如果要求一个类只能产生两三个对象呢?该怎么实现?

    这里可以对单例模式进行扩展,如下:

    public class SingletonDemo7 {
        
        //定义最多能产生的实例数量
        private static int maxNumOfSingletonDemo = 2;
        //定义一个列表,容纳所有的实例
        private static ArrayList<SingletonDemo7> SingletonDemoList=new ArrayList<SingletonDemo7>();
        //当前序列号
        private static int countNumOfSingletonDemo =0;
        //产生所有的对象
        static{
            for(int i=0;i<maxNumOfSingletonDemo;i++){
                SingletonDemoList.add(new SingletonDemo7());
            }
        }
        
        private SingletonDemo7(){
        }
        
        //随机获得一个对象
        public static SingletonDemo7 getInstance(){
        Random random = new Random();
        //随机
        countNumOfSingletonDemo = random.nextInt(maxNumOfSingletonDemo);
        return SingletonDemoList.get(countNumOfSingletonDemo);
        }
        
    }
  • 相关阅读:
    org.apache.maven.archiver.MavenArchiver.getManifest
    网易云信发送短信验证码
    background-attachment:fixed;
    background-size属性100% cover contain
    width:100% width:auto 区别
    背景图全屏显示
    多行文字的垂直居中
    路径问题../绝对路径/
    用position子绝父相和css3动画,当鼠标一上去元素从底部慢慢向上
    前置后置运算符重载
  • 原文地址:https://www.cnblogs.com/hezhh/p/5395667.html
Copyright © 2020-2023  润新知