栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

单例设计模式有哪些特点(单例设计模式)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

单例设计模式有哪些特点(单例设计模式)

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

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

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

实现方式

单例的几种常用实现方式

懒汉式(不推荐)

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

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

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"的那行),实际上可以分解成以下三个步骤:

    分配内存空间

    初始化对象

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

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

    分配内存空间

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

    初始化对象

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

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)

菜鸟教程|单例模式

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/776067.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号