关于的单例的几种形式以及他们的比较

本文详细介绍了单例模式的实现方式,包括懒汉式和饿汉式的不同变种,并探讨了它们在多线程环境下的表现及优化手段。此外,还讨论了枚举式单例的优点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天看了一下单例,所以想撸篇博客总结一下。也好自己复习复习。话不多少直接开始。

单例

单例是一种设计模式,来看看百度百科对单例模式的定义

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例

从实现角度来说,只要保证下面三点

一.是单例模式的类只提供私有的构造函数,
二.是类定义中含有一个该类的静态私有对象,
三.是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。

对单例的实现可以分为两大类——懒汉式和饿汉式,他们的区别在于:

  • 懒汉式:指全局的单例实例在第一次被使用时构建。
  • 饿汉式:指全局的单例实例在类装载时构建。

1.懒汉式
最基本的懒汉式单例,因为懒所以先为null,有人调用方法的时候再去new.这种方式对于单线程是没问题。多线程的时候有可能第一个线程判断为null,还没new完第二个线程也进来了。会出现new了两个对象的情况。

/**
 * 懒汉式单例:缺点在于没有考虑多线程。多线程情况下回出错 
 * @author<a href="mailto:953801304@qq.com">胡龙华</a>
 */
public class Singleton1 {
    private Singleton1(){
    }
    private static Singleton1 instance = null;
    public static Singleton1 Instance(){
        if(instance==null){
            instance = new Singleton1();
        }
        return instance;
    }
}

懒汉式变种一
懒汉式对于多线程问题,我们先到的解决办法是—加锁,可以解决两种多线程问题。但是这种方式并不好,因为你每次拿这个单例对象的时候都要上锁(加锁是一个很费时的过程)因此会效率很低。当你用户量很大时候影响就会变的明显

/**
 * 加锁的懒汉式:考虑的了多线程但是效率不高---因为加锁是一个很耗时的操作
 * @author<a href="mailto:953801304@qq.com">胡龙华</a>
 */

public class Singleton2 {
    private Singleton2(){
    }
    private static  Object synobj = new Object();
    private static Singleton2 instance = null;
    //方式一:锁类模板
    public static Singleton2 Instance(){
        synchronized (synobj) {
            if(instance==null){
                instance = new Singleton2();
            }
        }
        return instance;
    }
    //方式二:锁方法
//  public synchronized static Singleton2 Instance(){
//          if(instance==null){
//              instance = new Singleton2();
//          }
//      return instance;
//  }
}

懒汉式变种二
这种方式是是可行的,解决了多线程安全问题,同时效率也得到提升,只有第一次为空的时候才加锁。后面不为空的时候就可以直接拿,效率比上一种高一些—双重锁

/**
 * 这种方式的单例只在instance为空的时候才加锁,效率提高,同时也考虑了多线程问题 
 * @author<a href="mailto:953801304@qq.com">胡龙华</a>
 */
public class Singleton3 {
    private Singleton3(){
    }
    private static Object synObj = new Object();
    private static Singleton3 instance = null;
    public static Singleton3 Instance(){
        if(instance==null){
            synchronized (synObj) {
                if(instance==null){
                  instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}

懒汉式最终版本—volatile
因为在计算机系统存在指令重排序,只要不是原子性操作都会有可能被操作系统优化,进行指令重排序,导致代码执行的时候并不能按照我们看到的代码执行顺序执行,因此需要加入volatile关键字

volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障(什么是内存屏障?),这样,在它的赋值完成之前,就不用会调用读操作。

/**
 * @author<a href="mailto:953801304@qq.com">胡龙华</a>
 */
public class Single {
    // volatile 防止计算系统的指令重排序问题
    private static volatile Single instance = null;
    private Single(){};
    public static Single getInstance(){
        if(instance==null){ //为空才加锁,提高效率
            synchronized (Single.class) {
                if(instance==null){ //加锁后再判断一次,看似多余,其实是必须,用来防止多线程问题。
                    instance = new Single();
                }
            }
        }
        return instance;
    }
}

2.饿汉式
饿汉式,理解一下就不管那么多 先new一个。需要的时候就提供,这种方式是比较推荐的。先new一个耗费了内存,但是换取了时间,效率较高

/**
 * 饿汉式:推荐写法,用内存换时间,效率高。 
 * @author<a href="mailto:953801304@qq.com">胡龙华</a>
 */
public class Singleton4 {
    private Singleton4(){
    }
    private static final Singleton4 instance = new Singleton4();
    public static Singleton4 Instance(){
        return instance;
    }
}

这种方式的缺点:
* 无法控制new Instance的时间,当类加载器把类一加载就空new一个实例,初始化太早就会造成资源浪费
* 如果这个类初始化需要依赖其他资源,不能掌握初始化时间,可能会因为没有拿到依赖资源而造成的初始化失败

解决—-来自 Effective Java
静态内部类

/**
 * @author<a href="mailto:953801304@qq.com">胡龙华</a>
 */
public class InnerSingle {
    private InnerSingle(){
    }
    private static class SingletonHolder{
        private static final InnerSingle INSTANCE = new InnerSingle();
    }
    public static InnerSingle getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

巧妙之处:

  • 对于内部类SingletonHolder,它是一个饿汉式的单例实现,在SingletonHolder初始化的时候会由ClassLoader来保证同步,使INSTANCE是一个真·单例。
  • 同时,由于SingletonHolder是一个内部类,只在外部类的Singleton的getInstance()中被使用,所以它被加载的时机也就是在getInstance()方法第一次被调用的时候。
  • -

3.枚举式单例
它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,这种方式很强,以后要多用用。

/**
 * 枚举式单例
 * @author<a href="mailto:953801304@qq.com">胡龙华</a>
 */
public enum Singleton5 {
    INSTANCE;
    public void Hello(){
        System.out.println("Hello Singleton");
    }
}

这里写图片描述

明确实现lazy loading效果可以用下列方式

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

本篇博客,一部分内容是我参考剑指offer和参考别人总结的。写的很好我把地址贴上
单例的七种方式
聊聊单例设计模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值