单例模式中的懒汉式、饿汉式、双重检查、静态内部类的理解

在日常学习和开发中,单例模式经常遇到,想必大家多多少少都有些了解,比如:在Spring中创建的Bean实例默认都是单例模式存在的

文章目录

  • 一、什么是单例模式?
      • 1、单例模式概念
      • 2、单例模式的特点
      • 3、单例模式的好处
      • 4、应用场景
  • 二、实现单例模式的实例
      • 1、饿汉式
      • 2、懒汉式
      • 3、双重检查加锁单例模式(双检锁式)
      • 4、枚举类
      • 5、静态内部类
  • 三、总结

一、什么是单例模式?

1、单例模式概念

      就是一个类只有一个实列,而且自行实例化(自己创建实例)并向整个系统提供这个实例。

2、单例模式的特点

   a)只能有一个实例
   b)必须自己创建自己的唯一实例
   c)必须给所有其他对象提供这一实例

3、单例模式的好处

   a)减少系统性能开销,因为只生成一个实例
   b)确保所有对象都访问唯一实例

4、应用场景

   a)每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。

   b)网站的计数器,一般也是采用单例模式实现,否则难以同步。

   c)多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

   d) 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。

二、实现单例模式的实例

1、饿汉式

   a)饿汉式优点:线程安全,调用效率高

   b)饿汉式缺点:加载类时立即实例化,所以没有延时加载的优势;不管有没有用到这个实例都会创建,浪费资源

   c)饿汉式代码演示:

public class Hungry {
	//创建一个私有静态的变量,用来存对象
	//此处直接实例化,在类加载的时候就完成了实例化并保存在类中
	private static Hungry huangry=new Hungry();
	//私有化构造方法,外部无法调用构造方法创建该对象
	private Hungry() {		
	}
	//创建公共静态的方法,提供给其他类调用来获取对象
	public static Hungry getObject() {	
		return huangry;
	}
}

2、懒汉式

a)线程不安全的懒汉式(不推荐使用):最基础的懒汉式代码实现,下面代码只适用单线程,在多线程下不安全

public class Lazy {

	private static Lazy lazy;	
	private Lazy() {	
	}
	//调用该方法时才生成实例
	public static Lazy getObject() {
		if(lazy==null) {
			lazy=new Lazy();
		}	
		return lazy;
	}
}

b)线程安全的懒汉式(不推荐使用):在上一步的基础上加上synchronize锁,每一次执行时都要进行同步和判断,执行速度慢

public class Lazy {

	private static Lazy lazy;	
	private Lazy() {	
	}
	//使用synchronized关键字来确保只会生成单例,线程安全的
	public static synchronized Lazy getObject() {
		if(lazy==null) {
			lazy=new Lazy();
		}	
		return lazy;
	}
}

3、双重检查加锁单例模式(双检锁式)

在程序每次调用getInstance()方法时先不进行同步,而是在进入该方法后再去检查类实例是否存在,若不存在则进入接下来的同步代码块;进入同步代码块后将再次检查类实例是否存在,若不存在则创建一个新的实例,这样一来,就只需要在类实例初始化时进行一次同步判断即可,而非每次调用getInstance()方法时都进行同步判断,大大节省了时间。
将对象声明为volatitle后,重排序在多线程环境中将会被禁止

public class Singleton {
    // 私有构造
    private Singleton4() {}
    //加上volatile关键字,禁止重排序
    private static volatile Singleton single = null;
    // 同步代码块的内部和外部都判断了实例是否为空,
    //因为可能会有多个线程同时进入到同步代码块外的if判断中,
    public static Singleton getInstance() {
        if (single == null) {
            synchronized (Singleton.class) {
            	//如果在同步代码块内部不进行判空的话,
            	//可能会初始化多个实例。
                if (single == null) {
                	//JVM 的即时编译器中存在指令重排序的优化,single变量如果不加volatile修饰,可能会出错
                    single = new Singleton();
                }
            }
        }
        return single;
    }
}

4、枚举类

线程安全,但不支持延迟加载

//枚举式单例模式
public enum Singleton {
     //这个枚举元素,本身就是单例对象
    INSTANCE;
    //添加自己需要的操作
    public void print(){
    	System.out.println("hello world");
    }
}

调用方法:

public class Test {
    public static void main(String[] args) {
        Singleton.INSTANCE.print();
    }
}

5、静态内部类

资源利用最大化且线程安全

//在调用getInstance()才会加载,同时立即实例化对象,
//所有线程安全,且有延时加载的优势
public class Singleton {   
    private static class Instance{
    	//在静态内部类中实例化对象
        private static final Singleton instance=new Singleton();
    }
    private Singleton() {
    }
    public static Singleton getInstance(){
        return Instance.instance;
    }
}

三、总结

饿汉式:线程安全,效率也高,但浪费资源,没有延迟加载

懒汉式:有延迟加载,线程不安全,就算加上synchronize同步锁,线程安全了,但执行效率低

双重检查加锁(推荐使用):线程安全,效率高,一定要加上volatitle关键字,不然会因为JVM重排序偶尔会出现问题

枚举类(推荐使用):实现简单,枚举本身就是单例,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞!但没有延迟加载

静态内部类(推荐使用):线程安全,且有延时加载的优势

日常开发中如何选择哪种单例模式呢:

1,占用资源少,不需要延时加载,枚举式好于饿汉式;

2,占用资源多,需要延时加载,静态内部类式或双重检查加锁好于懒汉式;

欢迎大家阅读,本人见识有限,写的博客难免有错误或者疏忽的地方,还望各位大佬指点,在此表示感谢。

你可能感兴趣的:(23种设计模式,设计模式,java,多线程)