资讯专栏INFORMATION COLUMN

Java设计模式优化-单例模式

eccozhou / 655人阅读

摘要:单例模式概述单例模式是一种对象创建模式,用于产生一个类的具体事例。所以解决了线程安全问题参考失效原因和解决方案中单例模式的缺陷及单例的正确写法懒汉式静态内部类私有构造器获取单例的方法静态内部类持有单例作为静态属性。

单例模式概述

单例模式是一种对象创建模式,用于产生一个类的具体事例。使用单例模式可以确保整个系统中单例类只产生一个实例。有下面两大好处:

对于频繁创建的对象,节省初第一次实例化之后的创建时间。

由于new操作的减少,会降低系统内存的使用频率。减轻GC压力,从而缩短GC停顿时间

创建方式:

单例作为类的私有private属性

单例类拥有私有private构造函数

提供获取实例的public方法

单例模式的角色:

角色 作用
单例类 提供单例的工厂,返回类的单例实例
使用者 获取并使用单例类

类基本结构:

单例模式的实现 1.饿汉式
public class HungerSingleton {
    //1.饿汉式
    //私有构造器
    private HungerSingleton() {
        System.out.println("create HungerSingleton");
    }
    //私有单例属性
    private static HungerSingleton instance = new HungerSingleton();
    //获取单例的方法
    public static HungerSingleton getInstance() {
        return instance;
    }
}

注意:

单例修饰符为static JVM加载单例类加载时,直接初始化单例。无法延时加载。如果此单例一直未被使用,单Singleton 因为调用静态方法被初始化则会造成内存的浪费。

getInstance()使用static修饰,不用实例化可以直接使用Singleton.getInstance()获取单例。

由于单例由JVM加载类的时候创建,所以不存在线程安全问题。

2.简单懒汉式
public class Singleton {
    //2.1简单懒汉式(线程不安全)
    //私有构造器
    private Singleton() {
        System.out.println("create Singleton");
    }
    //私有单例属性[初始化为null]
    private static Singleton instance = null;
    //获取单例的方法
    public static Singleton getInstance() {
        if(instance == null) {
            //此处instance实例化
            //首次调用单例时会进入  达成延时加载
            instance = new Singleton();
        }
        return instance;
    }
}

由于未使用 synchronized 关键字,所以当线程1调用单例工厂方法Singleton.getInstance() 且 instance 未初始化完成时,线程2调用此方法会将instance判断为null,也会将instance重新实例化赋值,此时则产生了多个实例!

如需线程安全可以直接给getInstance方法上加synchronized关键字,如下:

public class Singleton {
    //2.2简单懒汉式(线程安全)
    //私有构造器
    private Singleton() {
        System.out.println("create Singleton");
    }
    //私有单例属性[初始化为null]
    private static Singleton instance = null;
    //获取单例的方法 将此方法使用synchronized关键字同步
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            //此处instance实例化
            //首次调用单例时会进入  达成延时加载
            instance = new Singleton();
        }
        return instance;
    }
}

面临的问题:

由于对getInstance()整个方法加锁,在多线程的环境中性能比较差。

3.DCL 懒汉式(双重检测)
简单懒汉式(线程安全)中,对getInstance()方法加锁,导致多线程中性能较差,那么是否可以减小锁的范围,使不用每次调用geInstance()方法时候都会去竞争锁?

DCL(Double Check Locking)双重检测 就是这样一种实现方式。

public class DCLLazySingleton {
    //3.DCL
    //私有构造器
    private DCLLazySingleton() {
        System.out.println("create DCLLazySingleton");
    }
    //私有单例属性[初始化为null] volatile 保证内存可见性 防止指令重排
    private static volatile DCLLazySingleton instance = null;//step1
    //获取单例的方法
    public static DCLLazySingleton getInstance() {
        //这里判null 是为了在instance有值时,不进入加锁的代码块,提高代码性能。
        if(instance == null) {
            //缩小锁范围 由于是静态方法方法调用的时候不依赖于实例化的对象 加锁只能使用类
            synchronized (DCLLazySingleton.class) {
                //这里判null 是为了配合volatile解决多线程安全问题
                if(instance == null) {
                    instance = new DCLLazySingleton();
                }
            }
        }
        return instance;
    }
}

注意:

传统DCL(step1处并未使用 volatile 或使用了但在JDK1.8之前)面临的问题:

由于初始化单例对象new DCLLazySingleton() 操作并不是原子操作,由于这是很多条指令,jvm可能会乱序执行。

在线程1初始化对象可能并未完成,但是此时已经instance对象已经不为null。(已经分配了内存,但是构造方法还未执行完【可能有一些属性的赋值未执行】)

此时线程2再获取instance 则不为null 直接返回。那么此时线程2获取的则为‘构造方法未执行完的instance对象’。则不能保证线程安全。

解决方式:

加上volatile关键字,volatile保证内存可见性,内存屏障,防止指令排!

加上volatile关键字后,线程2获取的构造方法未执行完的instance对象,会在线程1修改之后同步到线程2(volatile 内存空间)。所以解决了线程安全问题

参考:

DCL失效原因和解决方案

java 中单例模式DCL的缺陷及单例的正确写法

4.懒汉式(静态内部类)
public class StaticSingleton {
    //私有构造器
    private StaticSingleton() {
        System.out.println("create StaticSingleton!");
    }
    //获取单例的方法
    public static StaticSingleton getInstance() {
        return SingletonHolder.instance;
    }
    //静态内部类 持有单例 作为静态属性。
    //由于只有在访问属性时才会加载静态类初始化instance。所以实现了懒加载。且由于JVM保证了类的加载为线程安全,所以为线程安全的。
    private static class SingletonHolder {
        //私有单例属性
        private static StaticSingleton instance = new StaticSingleton();
    }
}

注意:

由于StaticSingleton类被加载时,内部的私有静态类SingletonHolder并不会被加载,所以并不会初始化单例instance,当getInstance()被调用时SingletonHolder.instance 才会加载SingletonHolder,由于JVM保证了类的加载为线程安全,因此线程安全。

此方式既可以做到延时加载,也不会因为同步关键字影响性能。是一种比较完善的实现。推荐使用

5.枚举单例
public enum EnumSingleton {
    INSTANCE();
    EnumSingleton() {
        System.out.println("create EnumSingleton");
    }
}

线程安全,且能够抵御反射与序列化。

推荐使用

例外情况

上述的单例实现方式还是会面临一些特殊情况不能保证唯一实例:

反射调用私有构造方法。

序列化后反序列化会生成多个对象。可以实现私有readResolve方法。readObject()如同虚设,直接使用readResolve替换原本返回值。如下:

    private Object readResolve () {
    //返回当前对象
    return instance;
    }

由于上述两情况比较特殊,所以没有特别关注。

参考书籍

《Java程序性能优化》 -葛一鸣 等编著

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75921.html

相关文章

  • Java设计模式-单例模式(Singleton Pattern)

    摘要:如果需要防范这种攻击,请修改构造函数,使其在被要求创建第二个实例时抛出异常。单例模式与单一职责原则有冲突。源码地址参考文献设计模式之禅 定义 单例模式是一个比较简单的模式,其定义如下: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 或者 Ensure a class has only one instance, and provide a global point of ac...

    k00baa 评论0 收藏0
  • Java单例模式实现

    摘要:所以,在版本前,双重检查锁形式的单例模式是无法保证线程安全的。 单例模式可能是代码最少的模式了,但是少不一定意味着简单,想要用好、用对单例模式,还真得费一番脑筋。本文对Java中常见的单例模式写法做了一个总结,如有错漏之处,恳请读者指正。 饿汉法 顾名思义,饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。代码如下: public class Singleton...

    jaysun 评论0 收藏0
  • Android中的设计模式单例模式

    摘要:总结单例是运用频率很高的模式,因为客户端没有高并发的情况,选择哪种方式并不会有太大的影响,出于效率考虑,推荐使用和静态内部类实现单例模式。 单例模式介绍 单例模式是应用最广的模式之一,也可能是很多人唯一会使用的设计模式。在应用单例模式时,单例对象的类必须保证只用一个实例存在。许多时候整个系统只需要一个全局对象,这样有利于我么能协调整个系统整体的行为。 单例模式的使用场景 确保某个类有且...

    yzd 评论0 收藏0
  • java基础——单例模式

    摘要:单例模式可以避免对资源的多重重用。单例模式可以在系统中设置全局的访问点,优化和共享资源访问。一个简单的单例模式场景运行结果一个管理多个单例的数组场景运行结果 单例模式的优缺点: 1 单例模式只能在内存中存在一个实例,减少了内存开支,特别是对一个对象需要频繁的创建和销毁时,而且创建和销毁又不能进行优化时,单例模式的优势就非常明显。 2 由于单例只生成一个实例,减少了系统的性能开销,当一...

    defcon 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<