单例模式

单例模式

定义

指一个类只有一个实例,且该类能自行创建这个实例的一种设计模式。

特点

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

应用

  1. Windows系统的任务管理器。
  2. Windows系统的回收站。
  3. 操作系统的文件系统,一个操作系统只能有一个文件系统。
  4. 数据库连接池的设计与实现。
  5. 多线程的线程池设计与实现。
  6. Spring中创建的Bean实例默认都是单例。
  7. Java-Web中,一个Servlet类只有一个实例。
  8. 等等...

实现(8种)

  1. 饿汉式(静态常量)【可用】

    优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
    缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

    public class Singleton {
        private final static Singleton INSTANCE=new Singleton();  
        
        private Singleton(){}
        
        public static Singleton getInstance() {
            return INSTANCE;
        }
    }
    
  1. 饿汉式(静态常量)【可用】

    这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

    public class Singleton {
        private static Singleton instance; 
        
        static {
            instace = new Singleton();
        }
        
        private Singleton(){}
        
        public static Singleton getInstance() {
            return instace;
        }
    }
    
  1. 懒汉式(线程不安全)【不可用】

    这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

    public class Singleton {
        private static Singleton instance; 
        
        private Singleton(){}
        
        public static Singleton getInstance() {
            if(instance == null){
                instance = new Singleton();
            }
            return instace;
        }
    }
    
  1. 懒汉式(线程安全,同步方法)【不推荐用】

    解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
    缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。

    public class Singleton {
        private static Singleton instance; 
        
        private Singleton(){}
        
        public static synchronized Singleton getInstance() {
            if(instance == null){
                instance = new Singleton();
            }
            return instace;
        }
    }
    
  1. 懒汉式(线程安全,同步代码块)【不推荐用】

    public class Singleton {
        private static Singleton instance; 
        
        private Singleton(){}
        
        public static Singleton getInstance() {
            if(instance == null){
                synchronized(Singleton){
                 singleton = new Singleton();                
                }
            }
            return instace;
        }
    }
    
  1. 懒汉式(线程安全,同步代码块,双重检查)【可用】

    public class Singleton {
        private static Singleton instance; 
        
        private Singleton(){}
        
        public static Singleton getInstance() {
            if(instance == null){
                synchronized(Singleton){
                    if(instance ==  null){
                        singleton = new Singleton();                    
                    }
                }
            }
            return instace;
        }
    }
    
  1. 懒汉式(线程安全,同步代码块,双重检查)【可用】

    这种方式采用了类装载的机制来保证初始化实例时只有一个线程。

    静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。

    类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

    优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

    public class Singleton {
        private static volatile Singleton instance; 
        
        private Singleton(){}
        
        private static class SingletonInstance{
            private static final Singleton INSTANCE = new Singleton();
        }
        
        public static Singleton getInstance() {
            return SingletonInstance.INSTANCE;
        }
    }
    
  1. 懒汉式(枚举)【最佳】

    这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

    这种方式是 Effective Java 作者 Josh Bloch 提倡的方式

    enum Singleton{
        INSTANCE;
        public void doSomething(){
            System.out.println("do something");
        }
    }
    
版本二:
  • 懒汉式:在需要使用对象的时候才会去创建对象

    public class LazySingleton {
        private byte[] data1 = new byte[1024 * 1024];
        
        private static LazySingleton instance = null;    
        
        private LazySingleton(){}    
        
        public static LazySingleton getInstance() {
            if(instance==null) {
                instance=new LazySingleton();
            }
            return instance;
        }
    }
    

    上面这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字,如下:

    public class LazySingleton {
        private byte[] data1 = new byte[1024 * 1024];
        
        private static LazySingleton instance = null;    
        
        private LazySingleton(){}    
        
        public synchronized static LazySingleton getInstance() {
            if(instance==null) {
                instance=new LazySingleton();
            }
            return instance;
        }
    }
    

    但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:\

    public class LazySingleton {
        private byte[] data1 = new byte[1024 * 1024];
        
        private static LazySingleton instance = null;    
        
        private LazySingleton(){}    
        
        public static LazySingleton getInstance() {
            if(instance==null) {
                synchronized (instance) {
                    if(instance == null){
                        instance=new LazySingleton();
                    }
                }
            }
            return instance;
        }
    }
    

    似乎解决了之前提到的问题,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的,

    看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:

    a>A、B线程同时进入了第一个if判断

    b>A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();

    c>由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。

    d>B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。

    e>此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

    所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更有难度,有挑战性。我们对该程序做进一步优化:饿汉模式。

  • 饿汉式:在类加载的时候已经创建好该单例对象。

    饿汉式单例是程序启动的时候就已经创建好了对象,有可能浪费空间,因为如果在该类中声明了许多内存空间,但却没有使用的话,就很浪费内存空间,因为饿汉式它是在程序启动的时候就已经创建好了。

    饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。

    public class HungrySingleton {
        //可能会浪费空间
        private byte[] data1 = new byte[1024 * 1024];
        
        private static final HungrySingleton instance=new HungrySingleton();
        
        private HungrySingleton(){}
        
        public static HungrySingleton getInstance() {
            return instance;
        }
    }
    

    实际情况是,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式:

    public class Singleton {
        private byte[] data1 = new byte[1024 * 1024];
        
        //此处使用一个内部类来维护单例
        private static class SingletonFactory{
            private static Singleton instance=new Singleton();  
        } 
        
        private HungrySingleton(){}
        
        public static Singleton getInstance() {
            return SingletonFactory.instance;
        }
        
        public Object readResolve(){
            return getTnstance();
        }
    }
    

比较详细:https://www.cnblogs.com/happy4java/p/11206105.html

https://blog.csdn.net/weixin_39428894/article/details/124285960

https://www.cnblogs.com/xbwang520/p/11505482.html

https://blog.csdn.net/chali1314/article/details/123826025

https://www.cnblogs.com/xiketang/p/15493954.html

https://zhuanlan.zhihu.com/p/34406410

https://blog.csdn.net/qq_50652600/article/details/123968328

https://blog.csdn.net/GJ_007/article/details/123874405

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容