设计模式~单例模式

目录

一、概述

二、饿汉式

三、懒汉式

3.1 基本实现:

3.2 指令重排


一、概述

什么是单例模式:在整个系统中,每个类仅有一个对象实例

应用场景:

  • 业务系统中仅需要一个对象的时候,redis连接对象
  • Spring IOC容器中的对象默认是单例的

单例模式的分类:

  • 饿汉式:在加载类的时候就创建对象;
  • 懒汉式:懒加载模式,在需要获取对象的时候才会创建对象

单例模式实现步骤:

  • 构造方法私有化
  • 私有的实例对象
  • 对外暴露获取实例对象的方法

二、饿汉式

饿汉式实现简单,而且不会涉及线程安全问题;对象在类加载的时候就会创建,但是缺点在于如果对象不被使用,就会一直占据系统资源

public class Singleton {

    // 饿汉式
    private static Singleton singleton = new Singleton();

    private Singleton() {
        System.out.println("生成一个实例");
    }

    public static Singleton getInstance() {

        return singleton;
    }
}

调用:

public static void main(String[] args) {
    new Singleton();  // 编译无法通过
    Singleton tareget = Singleton.getInstance();
}

三、懒汉式

实现方面,就是什么时候要获取实例对象,什么时候进行创建; 注意:懒汉式有线程安全问题

3.1 基本实现:

/**
 *  懒汉式实现
 */
public class SingletonLazy {
    
    // 1.私有成员变量,但不直接创建
    private SingletonLazy singletonLazy;
    
    // 2.构造函数私有化
    private SingletonLazy() {
        System.out.println("创建一个对象");
    }
    
    // 3.对外暴露获取对象的方法
    public SingletonLazy getInstance() {
        if (singletonLazy == null) {
            singletonLazy = new SingletonLazy();
        }
        return singletonLazy;
    }
}

         这个实现中,可能存在同时多个线程调用方法,导致最终创建的对象不是同一个,可以使用synchronized解决,但是考虑如果对整个方法上锁,整个执行效率会产生下降,因此应该考虑局部上锁。

        而且,这个锁一定上在 if 中,否则和对整个方法上锁没什么区别

// 3.对外暴露获取对象的方法
public SingletonLazy getInstance() {
	if (singletonLazy == null) {
		
		// 考虑这个部分是否有问题??
		
		synchronized (SingletonLazy.class) {
			singletonLazy = new SingletonLazy();   
		}
	}
	return singletonLazy;
}

        看注释的部分,可能存在一个场景:两个线程A, B同时进入 if 中,但是A先获取锁并创建对象,而B在获取锁之后又重新创建了对象,对象发生了变化;

        因此,存在一次锁内检查:

// 3.对外暴露获取对象的方法
public SingletonLazy getInstance() {
	if (singletonLazy == null) {

		synchronized (SingletonLazy.class) {
			if (singletonLazy == null) {
				singletonLazy = new SingletonLazy();   
			}
		}
	}
	return singletonLazy;
}

结论:双重检测锁定

3.2 指令重排

考虑对象的创建过程:

  • 分配空间给对象
  • 初始化对象
  • 设置对象的内存地址,此时instance != null

但是在JVM中可能由于优化的原因导致创建对象的指令发生混乱:如 1->2-> 3 变为 1 -> 3 -> 2

产生的现象:

当第一个线程拿到锁并且进入到第二个if方法后, 先分配对象内存空间, 然后再instance指向刚刚分配的内存地址, instance 已经不等于null, 但此时instance还没有初始化完成。如果这个时候又有一个线程来调用getInstance方法, 在第一个if的判断结果就为false, 于是直接返回还没有初始化完成的instance, 那么就很有可能产生异常。

而使用 volatile 加载实例对象上,可是保证 变量线程间可见以及禁止指令重排

懒汉式完整实现:

public class SingletonLazy {

    // 1.私有成员变量,但不直接创建
    private volatile SingletonLazy singletonLazy;

    // 2.构造函数私有化
    private SingletonLazy() {
        System.out.println("创建一个对象");
    }

    // 3.对外暴露获取对象的方法
    public SingletonLazy getInstance() {
        if (singletonLazy == null) {

            synchronized (SingletonLazy.class) {
                if (singletonLazy == null) {
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }
}

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