1.单例模式

单例模式属于创建型模式,是Java中最简单的设计模式之一。这种模式负责创建自己的对象,并且确保只创建一个对象,通过提供一种访问其唯一对象的方式,直接访问实例,不需要再实例化该类对象

解决问题:避免一个全局使用的类,频繁地创建与销毁

设计思路:创建一个SingleTon类,该类的构造方法设置为私有化,提供一个静态方法给外部访问

如何保证对象唯一性:

1.不允许其它程序用new创建该对象

2.在该类创建一个本类实例。

3.对外提供一个方法让其他程序可以获取该类对象。

步骤:

1.私有化该类构造函数。

2.通过new在本类中创建一个本类对象。

3.定义一个公有的方法,将创建的对象返回。

实现方式

单例的几种常用实现方式

懒汉式(不推荐)

懒汉顾名思义,在使用时才开始创建实例对象,不常用。懒汉式有线程不安全和线程安全两种实现方式

线程不安全。这种方式是最基础的实现方式,但有一个最大的问题,即不支持多线程,在多线程情况下不能正常工作。代码实现

public class Singleton {

    // 创建 Singleton 实例对象
    private static Singleton singleton;

    private Singleton() {
    }

    // 获取唯一可用对象
    public static Singleton getInstance() {
        if (singleton==null){
            singleton=new Singleton();
        }
        return singleton;
    }

    public void see() {
        System.out.println("see best scene...");
    }
}

线程安全。如过果希望线程安全,我们可以给上述代码加一个锁。与线程不安全的实现一样,第一次调用时才初始化,避免内存浪费。但是加锁会导致效率低,99%的情况下是不需要同步的

饿汉式(推荐)

饿汉在使用之前,已经提前初始化好实例。它通过classloader机制来保证多线程安全的,不需要加锁,但由于在类加载时就实例化,浪费内存。比较常用

public class Singleton {

    // 创建 Singleton 实例对象
    private static Singleton singleton=new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance(){
        return singleton;
    }
}

双重校验锁(特殊需求考虑)

双锁机制核心思想,是先判断对象是否已经被初始化,再决定要不要加锁。通过双锁机制能保证安全且在多线程情况下保持高性能。实现难度相对复杂。

在懒汉式线程安全实现中,通过 synchronized 关键字实现加锁的操作,能保证线程安全,并且在每一次调用时都会进行加锁操作(其实加锁只需要在第一次初始化时用到,后续调用都没必要加锁),会导致很大性能开销。

执行双重检查时,如果多个线程同时通过了第一次检查,并且其中一个线程首先通过了第二次检查并且实例化了对象,那剩余的通过了第一次检查的线程就不会再去实例化对象。这样,除了初始化时会出现加锁,只要instance被创建之后,再调用getInstance()函数都不会进入到加锁逻辑中,后续所有调用都会避免加锁而直接返回,解决了性能的消耗问题

public class Singleton {
    // 创建 Singleton 实例对象
    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {// 检查变量是否初始化,不去获取锁,如果已初始化直接返回
            synchronized (Singleton.class) {// 获取类锁
                if (singleton == null) {// 再次判断变量是否初始化,如果未初始化就初始化一个对象
                    singleton = new Singleton();// 实例化对象 Singleton
                }
            }
        }
        return singleton;
    }
}

这里存在一个隐患,多线程场景下可能导致访问未初始化对象。看下实例化对象的那行代码(标记为"//实例化对象 Singleton"的那行),实际上可以分解成以下三个步骤:

  1. 分配内存空间

  2. 初始化对象

  3. 将对象指向刚分配的内存空间

但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:

  1. 分配内存空间

  2. 将对象指向刚分配的内存空间

  3. 初始化对象

现在考虑重排序后,两个线程发生了以下调用:

Time

Thread A

Thread B

T1

检查到singleton为空

T2

获取锁

T3

再次检查到singleton为空

T4

为singleton分配内存空间

T5

将singleton指向内存空间

T6

检查到singleton不为空

T7

访问singleton(此时对象还未完成初始化)

T8

初始化singleton

在这种情况下,T7时刻线程B对uniqueSingleton的访问,访问的是一个初始化未完成的对象。

怎么解决呢?通过volatile关键字,一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后:

  1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  2)禁止进行指令重排序。

使用volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

下面代码只是增加了volatile ,就完美的实现了双锁检查机制

public class Singleton {
    // 创建 Singleton 实例对象
    private volatile static Singleton singleton; // volatile 防止重排序

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {// 检查变量是否初始化,不去获取锁,如果已初始化直接返回
            synchronized (Singleton.class) {// 获取类锁
                if (singleton == null) {// 再次判断变量是否初始化,如果未初始化就初始化一个对象
                    singleton = new Singleton();// 
                }
            }
        }
        return singleton;
    }
}

登记式/静态内部类

这种方式能达到双检锁方式一样的功效,但实现更简单。利用Java的静态内部类,当外部类Singleton被加载的时候,并不会创建SingletonHolder实例对象。只有当调用getInstance()方法时,SingletonHolder才会被加载,这个时候才会创建instance。instance的唯一性、创建过程的线程安全性,都由JVM来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

附录:

Java中的双重检查锁(double checked locking)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值