Java设计模式之单例模式

文章目录

    • 简介
    • 使用场景
    • 实现方式
    • 总结

简介

单例模式是一种常用的软件设计模式,其定义是该类只允许一个实例存在,所有使用的对都是同一个对象,当然Java中使用反射可以打破封装,即使在构造器私有化时也能够通过反射创建该类实例,当然我们这里并不考虑,否则单例将无法实现

使用场景

在很多应用场景,我们只需要一个对象即可,比如线程池、缓存、工具类对象、连接池对象、日志对象等,如果出现多个实例,那么程序将可能出现不可预知的错误,单实例不仅能够保证程序使用的准确性,同样也避免频繁创建和销毁对象所带来的开销

实现方式

实现步骤

  1. 将构造器私有化,避免外部使用构造器创建该类的实例。
  2. 提供一个获取该实例的方法,一般提供一个静态方法作为该实例的访问点。

主要有两种实现模式

  • 懒汉模式:第一次使用实例时进行初始化,也就是延迟加载的效果,可以避免不使用的情况而造成资源浪费,在实例初始化需要考虑多线程环境下的安全问题
  • 饿汉模式:加载类时进行实例初始化,创建了唯一的实例,不管是否需要用到,不需要考虑线程安全问题

下面来举例说明几种实现单例的方式

1)线程不安全的懒汉 - 多线程环境可能创建多个实例

package com.huawei.design.singleton;

/**
 * @author: [email protected]
 * @date: 2019/5/4 14:33
 * @description: 线程不安全懒汉模式实现
 */
public class UnsafeLazySingleton {

    private static UnsafeLazySingleton unsafeLazySingleton;

    //私有化构造器
    private UnsafeLazySingleton(){}

    //提供访问点
    public static UnsafeLazySingleton getInstance(){
        if (unsafeLazySingleton == null){
            unsafeLazySingleton = new UnsafeLazySingleton();
        }
        return unsafeLazySingleton;
    }
}

优点:达到了延迟实例化实例的效果,避免了资源浪费。
缺点:在单线程下可用,多线程环境下可能创建多个实例,导致单例遭到破坏。

在单线程环境可以使用,在多线程环境不推荐,应该使用下面线程安全的实现方式。
推荐指数-★

2)线程安全的懒汉-使用静态同步方法获取实例

package com.huawei.design.singleton;

/**
 * @author: [email protected]
 * @date: 2019/5/4 14:43
 * @description: 线程安全懒汉模式实现
 */
public class SafeLazySingleton {
    
    private static SafeLazySingleton safeLazySingleton;
    
    private SafeLazySingleton(){}
    
    public static synchronized SafeLazySingleton getInstance(){
        if (safeLazySingleton == null){
            safeLazySingleton = new SafeLazySingleton();
        }
        return safeLazySingleton;
    }
}

优点:既实现了延迟实例化实例的效果,也同时保证了线程安全。
缺点:如果在多线程环境下使用,每次获取实例都需要获取锁,竞争锁开销较大。

虽然进行了线程安全同步,但性能开销比较大。
推荐指数-★★

3)线程不安全的懒汉-使用锁代码方式

package com.huawei.design.singleton;

/**
 * @author: [email protected]
 * @date: 2019/5/4 14:33
 * @description: 线程不安全懒汉模式实现
 */
public class UnsafeLazySingleton {

    private static UnsafeLazySingleton unsafeLazySingleton;

    //私有化构造器
    private UnsafeLazySingleton(){}

    //提供访问点
    public static UnsafeLazySingleton getInstance(){
        if (unsafeLazySingleton == null){
            synchronized (UnsafeLazySingleton.class){
                unsafeLazySingleton = new UnsafeLazySingleton();
            }
        }
        return unsafeLazySingleton;
    }
}

优点:达到了延迟实例化实例的效果。
缺点:本质上不是线程安全的,为什么说是线程不安全的,当两个线程同时执行到synchronized代码块时,一个线程将阻塞等待获取锁,一个线程将获取锁,获取到锁的线程执行代码块内容,初始化实例,当执行完代码块内容,释放锁,返回创建的实例,这时候等待锁的线程获取锁,又会在执行一遍代码块的内容,重新初始化一个实例,将导致单例的破坏,这也就是下面Double Check的由来。

由于存在线程安全问题,不推荐使用。
推荐指数-★

4)线程安全的懒汉-双重检查(Double Check)

package com.huawei.design.singleton;

/**
 * @author: [email protected]
 * @date: 2019/5/4 14:43
 * @description: 线程安全懒汉模式实现
 */
public class SafeLazySingleton {

    //这里使用volatile关键字很重要
    private static volatile SafeLazySingleton safeLazySingleton;

    private SafeLazySingleton(){}

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

优点:使用了双重检查,避免了线程不安全,同时也避免了不必要的锁开销。
缺点:暂无。

这里为什么了需要使用volatile关键字来修饰实例变量,因为在执行 safeLazySingleton = new SafeLazySingleton();这行代码时,实际上分为如下3行伪代码执行
memory = allocate();//1:分配对象所需的内存空间
ctorInstance(memory);//2:初始化对象
safeLazySingleton = memory;//3:将safeLazySingleton 指向该内存地址

上面2和3之间,可能会在执行的时候重排序(JIT编译优化或者CPU乱序执行),也就是Out-of-order-writes,重排序之后那么就先将safeLazySingleton 指向该内存地址,然后进行初始化,但是如果在执行初始化之前,有另外一个线程访问获取实例,这时候safeLazySingleton 不为null但还未初始化,那么将返回一个尚未初始化的实例给这个线程,而这个线程在使用未初始化的实例出现未知的错误,使用volatile修改变量将可以达到紧致指令重排序的效果,从未避免上述情况的发生,详细原理可参见《双重检查锁定与延迟初始化》。

既满足了延迟初始化的效果,并且兼具性能,同时也是线程安全的。
推荐指数-★★★★

5)线程安全的饿汉-静态变量初始化

package com.huawei.design.singleton;

/**
 * @author: [email protected]
 * @date: 2019/5/4 14:33
 * @description: 线程安全饿汉模式实现
 */
public class SafeEagerSingleton {

    private static SafeEagerSingleton safeEagerSingleton = new SafeEagerSingleton();

    //私有化构造器
    private SafeEagerSingleton(){}

    //提供访问点
    public static SafeEagerSingleton getInstance(){
        return safeEagerSingleton;
    }
}

优点:实现简单,无线程同步问题。
缺点:在加载类时完成实例初始化。可能会造成资源浪费。
相比双重检查实现方式更为简单,且性能好,无线程安全问题,在必定需要使用这个实例的场景可以使用。
推荐指数-★★

6)线程安全的饿汉-静态代码块初始化

package com.huawei.design.singleton;

/**
 * @author: [email protected]
 * @date: 2019/5/4 14:33
 * @description: 线程安全饿汉模式实现
 */
public class SafeEagerSingleton {

    private static SafeEagerSingleton safeEagerSingleton;

    static {
        safeEagerSingleton = new SafeEagerSingleton();
    }
    
    //私有化构造器
    private SafeEagerSingleton(){}

    //提供访问点
    public static SafeEagerSingleton getInstance(){
        return safeEagerSingleton;
    }
}

优点:同上
缺点:同上
推荐指数-★★

7)线程安全的懒汉-静态内部类

package com.huawei.design.singleton;

/**
 * @author: [email protected]
 * @date: 2019/5/4 14:33
 * @description: 线程安全懒汉模式实现
 */
public class SafeLazySingleton {

    private static SafeLazySingleton safeLazySingleton;

    //私有化构造器
    private SafeLazySingleton(){}

    //提供访问点
    public static SafeLazySingleton getInstance(){
        return InnerClass.INSTANCE;
    }

    private static class InnerClass {
        private static final SafeLazySingleton INSTANCE = new SafeLazySingleton();
    }
}

优点:无线程同步问题,实现了延迟初始化效果,只有调用getInstance时才会装载内部类,才会创建实例
缺点:暂无
在实现延迟初始化的同时且线程安全,避免加锁,使用内部类时,先调用内部类的线程会获得类初始化锁,从而保证内部类的初始化安全
推荐指数-★★★★★

8)枚举

package com.huawei.design.singleton;

/**
 * @author: [email protected]
 * @date: 2019/5/4 15:53
 * @description:枚举实现单例
 */
public enum SafeEagerSingleton {

    INSTANCE;

    public void whateverMethod() {

    }
}

优点:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
缺点:使用的是枚举,而非类。
在《Effective Java》书中第三条有说明使用枚举来实现单例的最佳方法

推荐指数-★★★★★

总结

以上主要介绍了实现单例的8中方式,之所以有这么多种方式,来源于ava的特性发展以及各种场景的需要,具体使用还需根据实际的应用场景进行选择,没有最好的单例,只有最合适的单例
(转载请注明出处)

你可能感兴趣的:(Java设计模式,Java设计模式)