单例模式,史上最全,快速入门

前言:大家好,我是小熊,25届毕业生,目前在一家满意的公司实习。本篇文章将23种设计模式中的单例模式,此篇文章为一天学习一个设计模式系列文章,后面会分享其他模式知识。这些东西,刚开始应该是不全面的,但文章会一直更新的。

🧑个人简介:大家好,我是小熊,一个想吃鱼的男人😉😉
目前状况🎉:25届毕业生,在一家满意的公司实习👏👏

💕欢迎大家:这里是CSDN,我用来快速回顾知识准备面试的地方,欢迎来到我的博客😘

单例模式

目录

单例模式

关键思路

(1) 饿汉式(调用效率好,线程安全,不能延迟加载)

(2)懒汉式(为实现延迟加载,效率不是很好,线程也不安全)

(3)懒汉式(线程安全,影响性能)

(4)双重校验锁(double-checked locking)

(5)静态内部类(具备所有优良特点)

补充延申:


单例模式可以说是Java设计模式中最简单的一个了,它属于创建型模式。核心就是,对象只要由一个线程创建且只创建一次。主要是需要大量重复的使用同一个相同的对象,为减少内存消耗,才这么做的

关键思路


要做到一个类的实例只能由类自身进行创建,关键就是私有化其构造函数。且在该类内部,需要实现单例创建与否的判断,根据判断的结果,如果有则直接返回,如果没有则创建之后返回。

单例模式主要是应用在需要控制实例数量,节省系统资源的场景下。而其优点相应的就是减少了内存的开销,尤其是在频繁的创建和销毁实例的时候。但是它也有缺点,比如它就与单一职责原则冲突了,一个类只应该关心其内部逻辑的实现,而不应该插手外部对其的实例化。

代码实现
单例模式的代码实现其实很简单,关键点就是私有化其构造函数。但是结合不同的场景需求(单线程多线程)以及Java的特性(锁和类加载),就会有很多种实现方法,我这里总结了常见的五种。

(1) 饿汉式(调用效率好,线程安全,不能延迟加载)

饿汉式实现起来简单,它基于类加载机制,不用加锁也能避免多线程同步的问题,所以执行效率会比较高。但是这种方法有个缺点,就是在类加载的时候就会进行实例化,没有达到Lazy Loading的效果。该种方法的代码实现如下:

public class Singleton1 {

    private Singleton1() {

    }

    private static final Singleton1 instance = new Singleton1();

    public static Singleton1 getInstance() {
        return instance;
    }

}



(2)懒汉式(为实现延迟加载,效率不是很好,线程也不安全)

针对饿汉式中没有Lazy Loading的效果,可以通过如下方法来实现。不过该方法也有个最大的问题,就是只能支持在单线程环境下工作,它是线程不安全的,多线程的话会出现多个实例化对象的出现。具体代码如下:

public class Singleton2 {

    private Singleton2() {

    }

    private static Singleton2 instance;

    public static Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }

}


(3)懒汉式(线程安全,影响性能)

针对线程不安全的问题,最简单直接的解决方法就是加锁,对getInstance( )方法加锁,这能很快的解决多线程安全问题,但是带来的问题也很明显,就是太影响性能了。具体代码如下所示:

public class Singleton3 {

    private Singleton3() {

    }

    private static Singleton3 instance;

    synchronized public static Singleton3 getInstance() {
        if (instance == null) {
            instance = new Singleton3();
        }
        return instance;
    }

}


(4)双重校验锁(double-checked locking)

直接对整个getInstance( )方法上锁,能够解决线程安全问题,但会大大影响程序的性能,因为很容易出现锁竞争的情况。这里使用双重校验的机制,一方面能够解决线程安全问题,另一方面还能保持高性能。关键就是在于两个if判断,其实大部分情况下外部的if判断是不会通过的,因为只要该单例类被实例化了一次,外部if的判断条件就成立不了了,所以能够保持较好的性能;而内部的if判断,则针对的是单例类还没被实例化的时候,可能有多个线程同时通过了外部的if判断,要对单例类进行实例化,此时就需要上锁,并在临界区内再次判断实例是否已经被创建,防止多次创建,因此内部的if判断保证了只有一个实例会被创建。

volatile

但是,注意,此时属性需要加上volatile,

/**
 * 1.线程可见性:
 * 其他线程会立即看到这个变化,
 * 2.禁止指令重排:
 * new SingletonDemo3()这个操作实际上包含以下三个步骤:
 * 分配内存空间
 * 初始化对象
 * 引用赋值给singletonDemo3
 * 重排序后先引用赋值,才初始化,其他线程可能会看到一个尚未完全初始化的对象。
   导致程序出错
 */

具体代码如下:

public class Singleton4 {

    private Singleton4() {

    }

    private static volatile Singleton4 instance;

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

}


(5)静态内部类(具备所有优良特点)

该方法能够达到同双重校验方式相同的效果,且实现方法更为简单,不需要借助锁就能实现。主要是借助了类加载的特性,内部静态类只有在使用的时候才会进行加载,这就能够实现Lazy Loading;且类加载时只会加载一次,天然的线程安全保证。具体代码如下:

public class Singleton5 {

    private Singleton5() {

    }

    private static class InnerClass {
        private static final Singleton5 instance = new Singleton5();
    }

    public static Singleton5 getInstance() {
        return InnerClass.instance;
    }

}
/**
 * 问题:为什么这种内部静态类的方式,是线程安全的?
 * 答:首先要了解类加载过程中的最后一个阶段:即类的初始化,类的初始化阶本质就是执行类构造器的<clinit>方法。那么什么是<clinit>方法?
 * 这不是由程序员写的程序,而是根据代码由javac编译器生成的。它是由类里面所有的【静态成员的的赋值语句】和【静态代码块】组成的。JVM内部会保证一个类的
 * <clinit>方法在多线程环境下被正确的加锁同步,也就是说如果多个线程同时去进行“类的初始化”,那么只有一个线程会去执行类的<clinit>方法,其他的线程
 * 都要阻塞等待,直到这个线程执行完<clinit>方法。然后执行完<clinit>方法后,其他线程唤醒,但是不会再进入<clinit>方法。也就是说同一个加载器下,
 * 一个类型只会初始化一次。
 *
 * 那么回到这个代码中,这里的静态变量的赋值操作进行编译之后实际上就是一个<clinit>代码,当我们执行getInstance方法的时候,会导致SingletonClassInstance
 * 类的加载,类加载的最后会执行类的初始化,但是即使在多线程情况下,这个类的初始化的<clinit>代码也只会被执行一次,所以他只会有一个实例。
 */b

补充延申:

单例模式是为了让我们使用同一个对象,但是这种模式通过反射是可以被破坏的,单例模式的关键点在于constructor方法的私有化private,我们可以通过反射detDeclareConstructor()获取构造函数,从而构建新的对象。

At least,最后小熊建议各位

  • 15
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值