单例模式概念:
单例模式是一种创建型设计模式,使用该模式可以确保一个类只有一个实例,并且提供一个全局访问点(下述的那个静态公共方法)来获取该实例。
在单例模式中,类会私有化构造方法,然后定义一个用于存储单例实例的类变量(instance ),再提供一个公共的(public)静态(static)方法(通常命名为:getInstance()),允许其他类可以通过这个方法获取到单例实例(对象)。这样可以保证在整个程序中,任何尝试创建实例的方式都会返回同一个实例对象。
注意:
- 1、单例类只能有一个实例。(通过构造私有化+公共方法限制)
- 2、单例类必须自己创建自己的唯一实例。(构造私有化,其他类不能new出新的实例)
- 3、单例类必须给所有其他对象提供这一实例。(公共方法返回实例给其他类)
单例模式实现方式(3种):
-
饿汉式(Eager Initialization)单例模式:
在类加载时就创建实例对象,并在全局访问点直接返回该实例。这种方式下,实例在整个程序运行期间都是存在的,无论是否被使用。通过将实例定义为final
常量,确保只有一个实例,并且通过私有的构造方法禁止外部类直接创建实例。通过公有的静态方法来获取实例,保证全局唯一性并提供统一的访问方式。
示例代码:public class Eager { //事先创建好实例对象 private static final Eager instance = new Eager(); private Eager() {} // 构造方法私有化 public static Eager getInstance() { //全局访问点(一个公共的静态的用于返回相同实例给其他类的调用) return instance; //直接返回该实例 } }
-
懒汉式(Lazy Initialization)单例模式(线程不安全): 在首次访问时才创建实例对象。这种方式下,实例的创建是延迟进行的,从而节省了系统资源。
public class Single { private static Single instance; private Single (){} public static Single getInstance() { //如果instance 为空那么就创建一个对象 if (instance == null) { instance = new Single(); } return instance; } }
-
懒汉式(Lazy Initialization)单例模式(双重检查锁定):
使用双重检查锁定的方式,确保只有一个线程创建实例对象,提高性能和效率。通过使用volatile
关键字,解决了指令重排序可能导致的问题,保证可见性和有序性。在多线程环境下,懒汉式单例模式通过这种方式实现了延迟加载,并保证了线程安全。
示例代码:public class Lazy { //事先定义一个空的类变量 private static volatile LazySingleton instance; private Lazy() {} // 构造方法私有化 public static Lazy getInstance() { if (instance == null) { 检查实例是否已经被创建 synchronized (Lazy.class) {// 使用同步机制确保多线程环境下只有一个线程创建实例 if (instance == null) {// 再次检查实例是否已经被创建 instance = new LazySingleton();//首次访问时才创建实例对象 } } } return instance; //返回实例(是相同的) } }
-
静态内部类(Static Inner Class)单例模式:
在静态内部类中持有单例对象,并在访问时创建实例。这种方式兼具了饿汉式和懒汉式的优点,既实现了延迟加载,又保证了线程安全。使用静态内部类的方式实现单例模式,可以实现懒加载(延迟加载)的效果,只有在调用getInstance()
方法时才会加载静态内部类并初始化实例。而且由于静态内部类只会加载一次,是线程安全的,保证了在多线程环境下的正确性。利用静态内部类的特性,结合静态变量的初始化实现,可以简洁地实现线程安全的单例模式。
示例代码:public class StaticInner { private StaticInner() {} // 私有构造方法,防止其他类通过new关键字实例化该类 private static class LazyHolder { // 静态内部类,用于持有实例变量 // 在静态内部类中创建并初始化唯一实例 final固化 确保单例实例的不可变性和线程安全性 private static final StaticInner INSTANCE = newStaticInner(); } public static StaticInner getInstance() { // 公有静态方法,用于获取唯一实例 return LazyHolder.INSTANCE;// 返回静态内部类中持有的唯一实例 } }
总结:
懒汉模式和饿汉模式的区别:
饿汉模式 (线程安全)是在类加载的时候就创建了对象
懒汉模式(线程不安全)第一次调用的时候才创建对象(双重检查锁定保证线程安全性)
饿汉式线程之所以是安全的: 是因为在类加载的时候就已经创建了对象 没有多个线程资源抢占这一回事。
懒汉式线程之所以不安全,是因为其在单线程中是没有问题,但是在多线程中就会出现资源抢占问题。比如:对个线程同时 访问getInstance()方法 那么 在 if (instance == null) 绝大部分线程都没有被拦截住。因为 new Single() 是需要时间的,在这个时间内 instance == null 而不是instance != null 所以导致了。private static Single instance; 被来回的覆盖,导致数据丢失。
比如::线程1和线程2 ,线程1 执行 new Single();的时候,线程2也进来了,那么如果在线程2执行 new Single();;之前,线程1 对 Single 对象内的数据 进行 修改,那么,当线程2 new Single();成功之后就会将 线程1给覆盖了,也就导致线程1 数据丢失。
通过使用单例模式,我们可以确保在程序中只有一个实例对象,并且可以方便地通过静态方法访问该实例。这在需要全局共享同一个资源或者需要保证只有一个对象实例时非常有用。注意要注意在多线程环境下的线程安全问题,可以使用加锁或者使用双重检查锁定等方式来解决。