设计模式入门——单例模式

本文详细介绍了设计模式中的单例模式,包括其概念、重要性以及多种实现方式,如饿汉模式、静态内部类单例模式、懒汉模式、线程安全的懒汉模式和双重锁模式。推荐使用静态内部类单例模式,因其具备延迟初始化、线程安全且避免资源浪费的优点。同时,文章还提到了枚举单例模式和序列化对单例模式的影响,并给出了防止序列化破坏单例的解决方案。
摘要由CSDN通过智能技术生成

单例模式

什么是单例模式?

单例模式顾名思义就是整个类中有且仅有一个的实例对象。

单例模式的三个要点:

  • 有且仅有一个私有实例
  • 构造方法是私有的,以保证不会被多次创建
  • 提供一个访问单例对象的公有静态方法

单例模式分很多种,常见的有饿汉模式、懒汉模式、静态内部类单例模式和双重锁模式…


饿汉模式

饿汉模式,这个比喻可以理解成人饥饿的时候立马就想要吃东西,放到代码里,可以说想要获取一个对象的时候立马就可以得到。饿汉模式中的Singleton对象作为静态成员变量,它在类加载时就已经初始化了,所以想要使用此对象的时候可以立即获取。

代码示例:

public class Singleton{
    //1.静态成员:有且仅有一个实例
    private static Singleton singleton = new Singleton();
    //2.私有的构造方法
    private Singleton(){};
    //3.获取单例对象的静态方法
   	public static Singleton getInstance(){
        return singleton;
    }
}

饿汉模式的特点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如网站首页页面缓存)。
  • 避免对资源的多重占用(比如写文件操作)。
  • 线程安全。
  • 可能造成资源的浪费,类初始化的时候已经创建单例对象实例了,但是它可能不会被使用到。(使用静态内部类单例模式懒汉式可以解决)

静态内部类单例模式

对饿汉模式进行改进,由于饿汉模式的单例对象初始化不能延迟到它真正被使用的时候,那就想个办法来延迟初始化,静态内部类就可以做到这一点。

代码示例:

public class Singleton{
    //静态内部类
    public static class Inner{
    	//1.静态成员
        public static final Singleton INSTANCE = new Singleton();
    }
    //2.私有的构造方法
    private Singleton(){};
    //3.获取该单例对象的静态方法
    public static Singleton getInstance(){
        return Inner.INSTANCE;    //获取静态内部类中的静态成员
    }
}

这种方法的优势在于,Singleton加载时并不会初始化 INSTANCE。只有等到getInstance()方法被显式调用时,才会触发静态内部类Inner进行加载,从而初始化Singleton对象。

只有一个线程时,可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。

特点:

  • 延迟初始化,避免资源浪费
  • 线程安全

懒汉模式

懒汉模式与饿汉模式的区别在于:懒汉模式将单例对象的初始化延迟到其真正被使用时。

代码示例:

public class Singleton{
    //1.静态成员
    private static Singleton singleton;
    //2.私有的构造方法
    private Singleton(){};
    //3.访问此对象的公有静态方法
    public static getInstance(){
        if (singleton == null){
            singleton = new Singleton();
        } 
        return singleton;
    }
}


特点:

  • 延迟初始化。
  • 线程不安全。两个线程在单例对象还没创建时,同时执行if (singleton == null)语句,那么就会造成线程问题。所以懒汉模式实际上不能算是单例模式。


线程安全的懒汉模式

既然懒汉模式线程不安全,那就让获取实例的静态方法加上synchronized关键字,它就是线程安全的了。

但是这样就造成一个问题,就是资源的浪费,因为这样的方法效率比较低,绝大多数情况下此方法并不需要加锁。在单例对象已经存在时去获取这个对象,其实不需要线程同步的,可以直接返回instance。

public class Singleton {
    //1.定义实例
    private static Singleton instance;
    //2.私有构造方法
    private Singleton(){};
    //3.对外提供获取实例的静态方法,对该方法加锁
    public static synchronized Singleton getInstance() {
        //在对象被使用的时候才实例化
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

双重锁模式实现懒汉式

针对线程安全的懒汉模式,消除了不必要的加锁步骤,提高其效率;并使用双重校验彻底解决多线程问题。

public class Singleton {  
    //1.静态成员
    private volatile static Singleton singleton;   // 使用volatile限制singleton
    //2.私有的构造方法
    private Singleton (){};
    //3.访问单例对象的静态方法
    public static Singleton getSingleton() {
        //第1次检查
    	if (singleton == null) {   
        	synchronized (Singleton.class) {
        		//第2次检查
               	if (singleton == null) {
            		singleton = new Singleton();  //保证创建对象时前面的语句已经都执行了
        		}
        	}  
   		}  
    return singleton;   //最后执行返回对象语句
    }  
}


第1重检查:检查实例是否已经存在,避免重复上锁。

第2重检查:同步后避免多线程问题,如果没有加上volatile进行限制,有可能造成先执行了对象返回,然后再进行对象的初始化,这样就导致实际返回的单例是一个空对象。

关键字volatile可以说是java虚拟机中提供的最轻量级的同步机制。volatile类型的变量保证对所有线程的可见性;volatile类型的变量禁止指令重排序优化。

Singleton对象的创建在JVM中可能会进行重排序。用volatile关键字修饰instance变量,使得instance在读、写操作前后都会插入内存屏障,避免重排序。


枚举单例模式

在1.5之后,还有另外一种实现单例的方式,那就是使用枚举。枚举单例模式在《Effective Java》中推荐的单例模式之一。

public enum Singleton{
    //枚举单例对象
    INSTANCE;
    //访问单例对象的静态方法
//    public static Singleton getInstance(){
//        return INSTANCE;
//    }
}

其中,访问单例对象的静态方法可以省略,然后通过Singleton.INSTANCE进行操作。其次,枚举类是没有构造方法的,因此通过反射也不能创建其实例,所以是线程安全的。

它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

在以上所有的单例模式中,推荐静态内部类单例模式。主要是非常直观,即保证线程安全又保证唯一性。


序列化

由于序列化可以破坏单例。要想防止序列化对单例的破坏,只要在Singleton类中定义readResolve,以让实例唯一,就可以解决该问题:

private Object readResolve() throws ObjectStreamException{
        return singleton;
}


参考链接:

https://blog.csdn.net/jisuanji198509/article/details/81260487 Java中的静态成员何时被初始化?

https://www.jianshu.com/p/15106e9c4bf3 Java内存模型

https://blog.csdn.net/hla199106/article/details/44965315?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control 静态变量,成员变量,局部变量

很详细的介绍

https://www.hollischuang.com/archives/1373

https://www.jianshu.com/p/3bfd916f2bb2

https://www.cnblogs.com/crazy-wang-android/p/9054771.html

https://www.jianshu.com/p/c08ab7115f6d Volatile关键字理解

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值