JAVA设计模式-创建者模式-单例模式实现的7种方式

本文详细介绍了Java中单例模式的实现方式,包括饿汉式(静态常量、静态代码块)、懒汉式(线程不安全、线程安全的同步方法、双重检查锁)以及静态内部类和枚举实现。分析了各种实现的优缺点,重点讨论了线程安全和延迟加载。推荐使用静态内部类和枚举实现,它们既能保证线程安全又实现了延迟加载。
摘要由CSDN通过智能技术生成

单例模式三要素

1.私有的构造方法;
2.私有静态实例引用;
3.返回静态实例的静态公有方法。

单例模式的概念
单例对象的类必须保证只有一个实例存在

单例设计模式分类两种:
懒汉式:指全局的单例实例在第一次被使用时构建。
饿汉式:指全局的单例实例在类装载时构建。

如何保证单例
一般实例化类的对象时,基本都是通过new 的方式来实例化一个对象,其实就是调用了实例化类的默认构造方法,所以为了保证类只有一个对象,我们需要将类的对象设置为private
①控制类的创建,不让其他类创建本类的对象,即需要设置private属性
②在本类中定义一个本类的对象

饿汉模式

饿汉式(静态常量)

 //单例模式 - 饿汉模式
public class HungrySingleton {
     //1.使用一个变量来保存该类唯一的实例,
     //因为单例模式在一个程序中只能拥有一个实例,
     //由于static成员只有一份,我们可以使用static变量来保存
    public static final HungrySingleton hungrySingleton 
    = new HungrySingleton();
     //2.封装构造方法,防止该类被实例出新的对象
    private HungrySingleton(){};
     //3.获取该类的唯一实例对象
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

在线程访问单例对象之前就已经创建好了,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例。

饿汉式(静态代码块)

public class HungryStaticSingleton {
		//final:防止反射破坏单例
    public static final HungryStaticSingleton hungrySingleton;
 
    static {
        hungrySingleton = new HungryStaticSingleton();
    }
    private HungryStaticSingleton(){};
 
    public static HungryStaticSingleton getInstance(){
        return hungrySingleton;
    }
}

饿汉模式有缺点说明:

  1. 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
  2. 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费

懒汉模式

懒汉式(线程不安全,不加锁)

public class LazySinglton {
	//1.使用一个变量来保存该类唯一的实例,因为单例模式在一个程序中只能拥有一个实例,
	//由于static成员只有一份,我们可以使用static变量来保存
	//懒汉单例模式是在使用的时候创建对象,因此初始时对象不应该被创建
	private static LazySinglton lazy = null;
	//声明为私有 外部就不能new
	//2.封装构造方法,防止该类被实例出新的对象
	private LazySinglton(){};
	//此处存在线程安全问题
	//3.获取该类的唯一对象,如果没有就创建
	public static  LazySinglton getInstance(){
		if(lazy == null){
			lazy = new LazySinglton();
		}
		return lazy;
	}
}
  1. 存在线程安全问题 多线程 重复创建 违背单例模式初衷
  2. 结论:不推荐使用

懒汉式(线程安全,synchronized 加锁 )

public class LazySinglton {
    private static LazySinglton lazy = null;
 
    private LazySinglton(){};
 
    // synchronized防止线程安全 JDK1.6之后对synchronized  性能优化了不少,但还是存在线程安全问题	
    public static synchronized LazySinglton getInstance(){ //将对象锁住
        if(lazy == null){
            lazy = new LazySinglton();
        }
        return lazy;
    }
}

1)这种写法能够在多线程中很好的工作,但是每次调用getInstance方法时都需要进行同步,造成不必要的同步开销,导致了资源的浪费,而且大部分时候我们是用不到同步的。
2) 结论:不推荐使用

懒汉式(线程安全,双重锁 )

public class Singleton {
	  // 1.看这里
    // private static Singleton singleton;
	  // 2.声明一个静态变量,使用volatile关键字修饰
    private static volatile Singleton singleton;
    private Singleton() {
    }
		public static Singleton getInstance() {
		// 对实例进行判断,如果已经创建,则直接返回;如果没有创建则进入创建流程
        if (singleton == null) {
        	// 进行加锁处理,防止并发
            synchronized (Singleton.class) {
                // 在进行一次非空判断,防止在第一次判断到加锁之间,
                //有其他线程完成了对象的创建
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
    return singleton;
}
  1. 我们进行了两次 if (singleton == null) 检查,这就是“双重检查锁”这个名字的由来。这种写法是可以保证线程安全的,假设有两个线程同时到达 synchronized 语句块,那么实例化代码只会由其中先抢到锁的线程执行一次,而后抢到锁的线程会在第二个 if 判断中发现 singleton 不为 null,所以跳过创建实例的语句。再后面的其他线程再来调用 getInstance 方法时,只需判断第一次的 if (singleton == null) ,然后会跳过整个 if 块,直接 return 实例化后的对象。
    这种写法的优点是不仅线程安全,而且延迟加载、效率也更高。
  2. 结论:推荐使用

注意:该方法中变量singleton注释1,2区别:2使用 volatile关键字

在java内存模型中,volatile 关键字作用可以是保证可见性或者禁止指令重排。
这里是因为 singleton = new Singleton() ,
它并非是一个原子操作,事实上,在 JVM 中上述语句至少做了以下这 3 件事:

    第一步是给 singleton 分配内存空间;
    第二步开始调用 Singleton 的构造函数等,来初始化 singleton;
    第三步,将 singleton 对象指向分配的内存空间
    (执行完这步 singleton 就不是 null 了)。

这里需要留意一下 1-2-3 的顺序,因为存在指令重排序的优化,
也就是说第 2 步和第 3 步的顺序是不能保证的,最终的执行顺序,
可能是 1-2-3,也有可能是 1-3-2。

如果是 1-3-2,那么在第 3 步执行完以后,singleton 就不是 null 了,
可是这时第 2 步并没有执行,singleton 对象未完成初始化,
它的属性的值可能不是我们所预期的值。
假设此时线程 2 进入 getInstance 方法,由于 singleton 已经不是 null 了,
所以会通过第一重检查并直接返回,但其实这时的 singleton 并没有完成初始化,
所以使用这个实例的时候会报错,详细流程如下图所示

在这里插入图片描述

静态内部类

// 静态内部类完成, 推荐使用
public class Singleton {
	private static volatile Singleton instance;
	//构造器私有化
	private Singleton() {}
	//写一个静态内部类,该类中有一个静态属性 Singleton
	private static class SingletonInstance {
		private static final Singleton INSTANCE = new Singleton();
	}
	//提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCE
	public static synchronized Singleton getInstance() {
		return SingletonInstance.INSTANCE;
	}
}
  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  2. 静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  4. 结论:推荐使用

枚举

public class Singleton {
    private Singleton(){
    }   
    public static enum SingletonEnum {
        SINGLETON;//属性
        private Singleton instance = null;
        private SingletonEnum(){
            instance = new Singleton();
        }
        public Singleton getInstance(){
            return instance;
        }
    }
}
  1. 不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
  2. 结论:推荐使用
A a = new A();

new一个对象的简单分解动作:

  1. 分配对象的内存空间
  2. 初始化对象
  3. 设置引用指向分配的内存地址

其中2、3两步间会发生指令重排序,导致多线程时如果在初始化之前访问对象则会出现问题,单例模式的双重检测锁模式正是会存在这个问题。可以使用volatile来禁止指令重排序解决问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邢一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值