单例模式详解(附常见的7种单例模式源码)

单例模式(Singleton Pattern):保证一个类仅有一个对象,并提供一个访问它的全局访问点。(Ensure a class only has one instance,and provide a globe point of access to it.)

常见应用场景:

  1. Windows的Task Manager(任务管理器)就是很典型的单例模式
  2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
  3. 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
  4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
  5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作否则内容不好追加。
  6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
  7. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
  8. Application 也是单例的典型应用(Servlet编程中会涉及到)
  9. 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
  10. 在servlet编程中,每个Servlet也是单例
  11. 在spring MVC框架/struts1框架中,控制器对象也是单例
  12. 一个产品注册了一个商标,那么它就是单例的

单例模式的优点:

  1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动 时直接产生一个单例对象,然后永久驻留内存的方式来解决
  2. 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计 一个单例类,负责所有数据表的映射处理

常见单例模式有以下7种:

1.饿汉式:先创建后使用,线程安全,占用内存。代码如下:

/**
 * 饿汉式单例模式
 * @author cui_yonghua  https://blog.csdn.net/cui_yonghua/article/details/90512943
 */
public class ClassA {
    //1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
    private ClassA(){ }
    //2.在类的内部创建一个类的实例
    //类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
    private static final ClassA instance = new ClassA();
    //3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
    //方法没有同步,调用效率高!
    public static ClassA  getInstance(){
        return instance;
    }

    //测试
    public static void main(String[] args) {
        ClassA a = ClassA.getInstance();
        ClassA b = ClassA.getInstance();
        System.out.println(a==b);
    }
}

控制台输出的结果如下图:
在这里插入图片描述
2.懒汉式:用的时候才创建,线程不安全,加锁会影响效率。资源利用率高了,但是,每次调用getInstance()方法都要同步,并发效率较低。代码如下:

/**
 * 懒汉式单例模式
 * @author cui_yonghua  https://blog.csdn.net/cui_yonghua/article/details/90512943
 */
public class ClassB {
    //1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
    private ClassB(){ }
    //2.在类的内部创建一个类的实例
    private static ClassB instance ;
    //3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
    public static synchronized ClassB  getInstance(){
        if(instance == null) {
            instance = new ClassB();
        }
        return instance;
    }

    //测试
    public static void main(String[] args) {
        ClassB a = ClassB.getInstance();
        ClassB b = ClassB.getInstance();
        System.out.println(a==b);
    }
}

控制台输出的结果如下图:
在这里插入图片描述
3.静态内部类方式:也即饿汉式和懒汉式的组合,调用getInstance()方法时才创建,达到了类似懒汉式的效果,同时又是线程安全的。代码如下:

/**
 * 使用静态内部类方式实现单例模式
 * @author cui_yonghua  https://blog.csdn.net/cui_yonghua/article/details/90512943
 */
public class ClassC {
    //1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
    private ClassC(){ }
    //2.在类的内部创建一个类的实例
    private static class Holder{
        private static ClassC instance = new ClassC();
    }
    //3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
    public static ClassC  getInstance(){
        return Holder.instance;
    }

    //测试
    public static void main(String[] args) {
        ClassC a = ClassC.getInstance();
        ClassC b = ClassC.getInstance();
        System.out.println(a==b);
    }
}

控制台输出的结果如下图:
在这里插入图片描述
4.枚举方法:线程安全,实现简单,调用效率高,不能延时加载。枚举本身就是单例模式,由JVM从根本上提供保障并且可以天然的防止反射和反序列化漏洞!需要继承的场景它就不适用了。枚举方式是Effective Java作者提倡的方式。代码如下:

/**
 * 使用枚举方法实现单例模式
 * @author cui_yonghua  https://blog.csdn.net/cui_yonghua/article/details/90512943
 */
public enum ClassD {
    //定义一个枚举的元素,它就代表了Singleton的一个实例。
    INSTANCE;
    //对外部提供调用方法:将创建的对象返回,只能通过类来调用
    public void otherMethod(){
        //功能处理
    }
    
    //测试
    public static void main(String[] args) {
        ClassD a = ClassD.INSTANCE;
        ClassD b = ClassD.INSTANCE;
        System.out.println(a==b);
    }
}

5.双重校验锁式:通常线程安全,加volatile的作用是禁止指令重排。(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)代码如下:

/**
 * 使用双重校验锁来实现单例模式
 * @author cui_yonghua  https://blog.csdn.net/cui_yonghua/article/details/90512943
 */
public class ClassE {
    //1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
    private ClassE(){ }
    //2.在类的内部创建一个类的实例
    private volatile static ClassE instance; //volatile作用:保证多线程可以正确处理instance
    //3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
    public static ClassE  getInstance(){
        if(instance == null){ //检查实例,如果为空,就进入同步代码块
            synchronized (ClassE.class){
                if(instance == null){ //再检查一次,仍未空才创建实例
                    instance = new ClassE();
                }
            }
        }
        return instance;
    }

    //测试
    public static void main(String[] args) {
        ClassE a = ClassE.getInstance();
        ClassE b = ClassE.getInstance();
        System.out.println(a==b);
    }
}

控制台输出的结果如下图:
在这里插入图片描述

6.使用ThreadLocal实现:线程安全,ThreadLocal采用以空间换时间的方式,为每一个线程都提供一份变量,因此可以同时访问而互不影响。代码如下:

/**
 * 使用ThreadLocal实现单例模式
 * @author cui_yonghua  https://blog.csdn.net/cui_yonghua/article/details/90512943
 */
public class ClassF {
    //1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
    private ClassF(){ }
    //2.在类的内部创建一个类的实例
    private static final ThreadLocal<ClassF> tls = new ThreadLocal<ClassF>(){
        @Override
        protected ClassF initialValue(){
            return new ClassF();
        }
    };
    //3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
    public static ClassF  getInstance(){
        return tls.get();
    }

    //测试
    public static void main(String[] args) {
        ClassF a = ClassF.getInstance();
        ClassF b = ClassF.getInstance();
        System.out.println(a==b);
    }
}

控制台输出的结果如下图:
在这里插入图片描述
7.使用CAS锁来实现:(CAS锁(Compare and Swap):比较并交换,是一种有名的无锁算法,属于乐观锁)。用CAS锁来实现单例模式是线程安全的,代码如下:

/**
 * 使用CAS锁来实现单例模式
 * @author cui_yonghua  https://blog.csdn.net/cui_yonghua/article/details/90512943
 */
public class ClassG {
    //1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
    private ClassG(){ }
    //2.在类的内部创建一个类的实例
    private static final AtomicReference<ClassG> instance = new AtomicReference<ClassG>(); 
    //3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
    public static final ClassG getInstance(){
        for(;;){
            ClassG current = instance.get();
            if(current != null){
                return current;
            }
            current = new ClassG();
            if(instance.compareAndSet(null,current)){
                return current;
            }
        }
    }

    //测试
    public static void main(String[] args) {
        ClassG a = ClassG.getInstance();
        ClassG b = ClassG.getInstance();
        System.out.println(a==b);
    }
}

控制台输出的结果如下图:
在这里插入图片描述
如果如果想了解更多设计模式,可点击:设计模式概述 以及 23种设计模式的介绍

如果觉得文章写的不错,也可以小小地打赏一下嘛~ 也期待合作,“码”上改变~

在这里插入图片描述

发布了299 篇原创文章 · 获赞 77 · 访问量 8万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 创作都市 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览