🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
文章目录
1.0 单例模式的概述
2.0 单例模式 - 饿汉式单例
2.1 关于饿汉式单例的线程安全问题
3.0 单例模式 - 懒汉式单例
3.1 关于懒汉式单例的线程安全问题
3.1.1 加锁 synchronized 方法
3.1.2 双重检查锁定
1.0 单例模式的概述
单例模式是一种常见的设计模式,其核心思想是确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。单例模式通常用于需要全局访问点且只需要一个实例的情况。
单例特点包括:
1)一个类只有一个实例对象。
2)提供全局访问点,允许其对象访问这个实例。
3)通过静态方法或静态变量实现。
常见的单例模式实现方法包括:
1)饿汉式单例:在类加载时即创建实例。
2)懒汉式单例:在第一次使用时创建实例。
使用单例模式的好处:
使用单例模式可以避免多次实例化对象,节省内存资源,并且提供一个统一的访问点,方便管理和维护。
2.0 单例模式 - 饿汉式单例
“饿汉”顾名思义,有迫不及待的意思,看到食物就迫切想吃。因此,在类加载的时候,需要立即创建实例对象。为了保证只有一个实例对象,对外的静态对象实例需要封装起来,且对外的构造器也需要封装起来,对外有一个获取实例对象的静态方法即可。
代码如下:
public class Singleton { // 在类加载时即创建实例对象 private static Singleton instance = new Singleton(); // 将构造器设为私有,外部无法直接实例化 private Singleton() { } // 提供一个公共的静态方法来获取实例对象 public static Singleton getInstance() { return instance; } // 其他方法和属性 }
通过这种方式,可以保证在程序运行过程中始终只有一个实例对象存在,且外部通过静态方法 getInstance() 来获取实例对象,确保了单例模式的实现。这种方式适合在程序初始化时就需要创建实例对象的情况下使用。
2.1 关于饿汉式单例的线程安全问题
在“饿汉式”单例模式中,由于在类加载时就创建实例对象,并且在静态变量中直接初始化实例,因此在多线程环境下是线程安全的。
类加载过程是由类加载器负责,保证了在多线程环境下只会加载一次类,因此也只会创建一个实例对象。
因此,如果使用“饿汉式”单例模式,在多线程环境下不需要额外的同步措施,可以保证线程安全。每个线程在访问该单例类的时候都会得到同一个实例对象,不会出现多个实例对象被创建的情况。
3.0 单例模式 - 懒汉式单例
当需要获取第一个实例对象的时候,才会去创建实例对象。
为了保证该类只有一个实例对象,需要对外的构造方法封装,在还没获取实例对象的时候,将静态变量赋值为 null 。再提供一个静态方法来获取实例对象,当获取实例对象的时候,先会判断还对象的变量是否为 null ,如果为 null 时,这时候需要立即创建对象,然后返回该对象;如果实例对象的变量不为 null 时,直接方法该实例对象。
代码如下:
public class SingletonLazy { private static SingletonLazy singleton = null; //私有的构造方法 private SingletonLazy(){ } //获取实例对象的方法,变量不为 null 的时候直接返回该实例对象 public static SingletonLazy getInstance(){ if (singleton == null){ singleton = new SingletonLazy(); } return singleton; } //其他方法和属性 }
3.1 关于懒汉式单例的线程安全问题
懒汉式单例模式在多线程环境下存在安全问题,需要通过加锁等方式来解决。
在懒汉单例模式中,实例对象在第一次被调用时才会被创建,如果这时候,多个线程同时调用 getInstance() 方法,可能会导致多个实例对象被创建,破环了单例的特性。
解决方法:可以通过加锁方式来确保线程安全
3.1.1 加锁 synchronized 方法
在 getInstance() 方法上加上 synchronized 关键字,确保在多线程环境下只有一个线程可以进入该方法,保证只创建一个实例对象。但是这种方式会影响性能,因为每次调用 getInstance() 都需要获取锁。
代码如下:
public class SingletonLazy { private static SingletonLazy singleton = null; //私有的构造方法 private SingletonLazy(){ } //获取实例对象的方法,变量不为 null 的时候直接返回该实例对象 public static synchronized SingletonLazy getInstance(){ if (singleton == null){ singleton = new SingletonLazy(); } return singleton; } //其他方法 public void fun(){ System.out.println("懒汉单例模式"); } }
在静态方法 getInstance() 方法上加上了 synchronized 修饰。每当多线程抢占获取锁的时候,没有获取锁的线程需要阻塞等待。这就保证了线程安全问题。
3.1.2 双重检查锁定
从代码执行效率这方面来看,每一个获取实例对象的时候,都需要阻塞等待,且只有创建对象的时候才需要真正的用到锁这个作用,其余大部分不需要来创建实例对象,只需要获取实例对象。
在 getInstance() 方法中进行双重检查,先判断实例是否已经被创建,如果没有再进行加锁创建实例。这种方式可以减少加锁的次数,提高性能。
优化代码如下:
public class SingletonLazy { private static volatile SingletonLazy singleton = null; //私有的构造方法 private SingletonLazy(){ } //获取实例对象的方法,变量不为 null 的时候直接返回该实例对象 public static SingletonLazy getInstance(){ if (singleton == null){ synchronized (SingletonLazy.class){ if (singleton == null){ singleton = new SingletonLazy(); } } } return singleton; } //其他方法 public void fun(){ System.out.println("懒汉单例模式"); } }
第一个 if 判断是否要加锁,实例化之后,线程自然安全,就无需加锁了,实例化之前, new 之前,就应该加锁。
第二个 if 判断是否要创建对象。
通过双重 if 避免了不可重复读带来的负面影响,避免了重复创建对象。