51、Java实现单例模式

本文详细介绍了Java中单例模式的六种实现方式,包括懒汉式和饿汉式的不同变体,以及推荐使用的静态内部类和枚举方式。探讨了每种方式的特点和适用场景。

在Java面试,单例模式是面试官经常问到的一个知识点。

  • 单例模式类型
  • 不同类型有什么特点
  • 手写多个单例模式
一、单例模式类型
  • 懒汉式单例
    • 1、适用于单线程环境(不推荐)
    • 2、适用于多线程环境,但效率不高(不推荐)
    • 3、双重检验锁
    • 4、静态内部类方式(推荐)
  • 饿汉式单例
    • 1、饿汉式(推荐)
    • 2、枚举方式(推荐)

1、懒汉式 单线程环境

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

此方式在单线程的时候工作正常,但在多线程的情况下就有问题了。如果两个线程同时运行到判断instance是否为null的if语句,并且instance的确没有被创建时,那么两个线程都会创建一个实例,

2、懒汉式 多线程环境,但效率不高

 private static SingleInstance instance;
    private static synchronized SingleInstance getInstance(){
        if(instance ==null){
            instance = new SingleInstance();
        }
        return instance;
    }

在多线程环境下我们还是只能得到该类的一个实例,只需要在getInstance()方法加上同步关键字sychronized,就可以了。但每次调用getInstance()方法时都被synchronized关键字锁住了,会引起线程阻塞,影响程序的性能。

3、双重检验锁

	//  static volatile  注意
    private static volatile SingleInstance instance;
    private SingleInstance(){
    }

    public static SingleInstance getInstance(){
        if(instance ==null){ // 同volatile 防止
        	// 锁住一个类
            synchronized (SingleInstance.class){
                if(instance ==null){
                    instance =new SingleInstance();
                }
            }
        }
        return instance;
    }

在多线程环境下,不影响程序的性能,不让线程每次调用getInstance()方法时都加锁,而只是在实例未被创建时再加锁,在加锁处理里面还需要判断一次实例是否已存在。

instance =new SingleInstance(); 它并不是一个原子操作。事实上,它可以”抽象“为下面几条JVM指令

	memory = allocate();    //1:分配对象的内存空间
	initInstance(memory);   //2:初始化对象
	instance = memory;      //3:设置instance指向刚分配的内存地址

上面操作2依赖于操作1,但是操作3并不依赖于操作2,所以JVM可以以“优化”为目的对它们进行 重排序,经过重排序后如下:

memory = allocate();    //1:分配对象的内存空间
instance = memory;      //3:设置instance指向刚分配的内存地址
//(此时对象还未初始化)
ctorInstance(memory);   //2:初始化对象

可以看到指令重排之后,操作 3 排在了操作 2 之前,即引用instance指向内存memory时,这段崭新的内存还没有初始化——即,引用instance指向了一个"被部分初始化的对象"。此时,如果另一个线程调用getInstance方法,由于instance已经指向了一块内存空间,从而if条件判为false,方法返回instance引用,用户得到了没有完成初始化的“半个”单例。

解决这个该问题,只需要将instance声明为volatile变量:
private static volatile Singleton instance;

4、静态内部类方式

    // 二、 静态内部类方式
    private SingleInstance(){
    }
    private static class HoldInstance{
        private final static SingleInstance instance = new SingleInstance();
    }

    public static SingleInstance getInstance(){
        return HoldInstance.instance;
    }

加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。 由于在调用 StaticSingleton.getInstance() 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的;由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。
总结:
优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。
劣势:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。

5、饿汉式

    // 一、饿汉式 线程安全
    private static final SingleInstance instance = new SingleInstance();
    private SingleInstance(){
    }
    public static SingleInstance getInstance(){
        return instance;
    }

饿汉式单例类:在类初始化时,已经自行实例化。

6、枚举方式(推荐)

public class Singleton {
    public static void main(String[] args) {
        Single single = Single.SINGLE;
        single.print();
    }

    enum Single {
        SINGLE;

        private Single() {
        }

        public void print() {
            System.out.println("hello world");
        }
    }
}

创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量)。

参考链接
Java实现单例模式(懒汉式、饿汉式、双重检验锁、静态内部类方式、枚举方式)

单例模式的七种写法

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值