单例模式
1、了解概念:
单例模式是指采用一定的方法保证在整个软件系统种,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
个人理解:一个类只能实例化一个对象,其他的使用都是引用这个对象。
单例模式的八种实现方式
1、饿汉式 a、静态常量(推荐) b、静态代码块(推荐) 2、懒汉式 a、线程不安全 b、线程安全,同步方法 c、线程安全,同步代码块 3、双重检查(推荐) 4、静态内部类(推荐) 5、枚举(推荐)
单例模式的作用:
1、节省资源:有些对象在程序中只需要一个实例,如果反复创建多个实例会造成资源的浪费,而单例模式可以确保只创建一个实例,节约资源。 2、全局访问点:是单例模式中提供给其他代码访问单例对象的接口或方法,它封装了单例对象的创建和访问逻辑,使得其他部分可以方便地获取单例对象,同时可以控制访问权限和简化调用方式。 3、保持一致性:单例模式可以确保一个类只有一个实例,从而避免了多个实例之间状态的不一致性。 4、延迟初始化:有些对象的初始化比较耗时,使用单例模式可以将对象的初始化延迟到真正需要的时候进行,提高程序的性能。 5、线程安全:单例模式可以保证在多线程环境下只有一个实例被创建,避免了多线程并发访问时可能出现的问题。
饿汉式
1、静态常量
步骤:
1、构造器私有化
2、类的内部创建对象
3、向外暴露一个静态公共方法(getInstance)
4、代码实现
public class Singleton {
// 创建静态常量实例
private static final Singleton instance = new Singleton();
// 私有构造函数,禁止外部通过new关键字创建实例
private Singleton() {
// 在此处进行初始化操作
}
// 公共方法,用于获取实例
public static Singleton getInstance() {
return instance;
}
}
优点:
- 写法简单,线程安全、效率高,在类装载的时候完成实例化,避免了线程同步问题
缺点:
内存浪费:在类加载的时候完成了实例化,
无法实现懒加载:没有达到Lazy Loading的效果
不支持传参
说明:
- 这种方式基于classloder机制避免了多线程同步问题,但是instance在类装载时就实例化,单例模式中大多数是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他静态方法导致类装载,这个时候初始化instance就没有导致lazy loading的效果
结论:
- 这种单例模式可用,但是可能会造成浪费内存
补充:lazy loading
Lazy loading(延迟加载)是一种设计模式,它指的是在需要的时候才加载数据或资源,而不是在初始化时就立即加载。这样可以延迟创建或数据加载时间,实现节省资源和提高性能。 使用场景: 1、减少启动时间:延迟加载启动时不需要的资源,加快程序启动时间。 2、节省资源:有些资源可能占用较大内存或需要较长时间初始化,如果不立即加载可以节省资源。 3、提高性能:延迟加载可以根据实际需要动态加载资源,避免了一开始就加载所有资源导致性能下降。
2、静态代码块
public class Singleton {
private static final Singleton instance;
static {
try {
instance = new Singleton();
} catch (Exception e) {
throw new RuntimeException("Exception occurred in creating singleton instance");
}
}
private Singleton() {
// 私有构造函数
}
public static Singleton getInstance() {
return instance;
}
}
这种方式跟静态常量的优缺点差不多。
懒汉式
1、线程不安全
public class Singleton {
private static Singleton instance;
private Singleton() {
// 私有构造函数
}
//提供一个静态的公共方法,使用到该方法时,才创建instance
//即 懒汉式
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优缺点说明:
1、起到了Lazy Loading效果,但是只能在单线程中使用 2、如果在多线程情况,一个线程进入了if(instance == null)判断语句块,还没来得及向下执行,另一个线程也通过了这个判断语句,这是会产生多个实例。 3、总结:多线程不可使用这种方式,在实际开发中也不要使用这种方式。
2、线程安全,同步方法
public class Singleton {
private static Singleton instance;
private Singleton() {
// 私有构造函数
}
//加入了同步,解决了线程安全问题
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优缺点说明:
1、解决了线程不安全问题 2、效率太低了。每个线程想要类的实例化的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后续需要直接return就行了 3、结论:实际开发,不推荐
3、线程安全,同步代码块
public class Singleton {
private static Singleton instance;
private Singleton() {
// 私有构造函数
}
//加入了同步,解决了线程安全问题
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
直接总结:本意是想对上种方式优化,实际并没有优化,还是会出现不同步情况。
结论:实际开发中,不能使用这种方式。
双重检查
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
// 私有构造函数
}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查,避免不必要的同步
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查,确保只有一个实例被创建
instance = new Singleton();
}
}
}
return instance;
}
}
优缺点说明:
1、Double-Check是多线程开发中常使用的,如上述代码,我们使用了两次if(singleton == null) 检查,这样就可保证线程安全了 2、特点:线程安全、延迟加载、效率较高。推荐使用 3、使用双重检查锁定实现线程安全的单例模式既可以确保线程安全,又可以尽量减少同步代码块的使用,从而提高性能。
补充:
volatile是Java的关键字。 用于: 1、声明变量具有可见性: 当一个线程修改了一个被 volatile 修饰的变量的值时,这个新值对其他线程是立即可见的,即所有线程都能看到最新的值。 2、禁止重排列: volatile 关键字会禁止 JVM 对代码的优化重排序,保证指令不会被重排,从而避免出现意外的执行顺序。 3、不保证原子性: volatile 关键字只能确保可见性和禁止重排序,并不能保证对 volatile 变量的操作是原子性的。如果需要保证原子性
静态内部类
public class Singleton {
private Singleton() {
// 私有构造函数
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
利用静态内部类特点:
外部类被装载了,静态内部类不会被装载
在调用getInstance方法的时候,会实现静态内部类只被装载一次
优缺点说明:
1、这种方式次啊用类装载机制来保证初始化实例时只有一个线程 2、静态内部类在Singleton类被装载时并不会立刻实例化,而是在需要实例化时才调用getInstance方法,才会装载SingletonInstance类,从而实现Singleton的实例化 3、类的静态属性智慧在第一次加载类的时候初始化,所以,在这JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程无法进入 4、优点:避免了线程不安全,利用静态内部类特点实现了延迟加载,效率高 5、结论:推荐使用
枚举
public enum Singleton {
INSTANCE;
// 可以在枚举中定义自己需要的方法和属性
public void doSomething() {
// 执行相应的操作
}
}
优缺点说明:
1、通过枚举实现单例模式,可以避免多线程问题,还可防止反序列化重新创建新的对象。 2、结论:推荐使用
单例模式在JDK中使用
比如说Runtime类中使用到了饿汉式
小结:
1、饿汉式 a、静态常量(推荐) b、静态代码块(推荐) 2、懒汉式 a、线程不安全 b、线程安全,同步方法 c、线程安全,同步代码块 3、双重检查(推荐) 4、静态内部类(推荐) 5、枚举(推荐)
饿汉式:
静态常量:推荐使用,简单、线程安全,在类加载时就创建实例。
静态代码块:不太推荐,与静态常量方式类似,但可读性稍差。
懒汉式:
线程不安全:不推荐,多线程环境下存在安全风险。
线程安全,同步方法:不推荐,性能较差,每次获取实例都需要同步。
线程安全,同步代码块:一般不推荐,和同步方法类似,同步块也会影响性能。
双重检查:
- 推荐使用:在多线程环境下,确保了延迟加载和高性能的需求。
静态内部类:
- 推荐使用:延迟加载、线程安全、高性能,且实现简洁。
枚举:
- 强烈推荐使用:简洁、线程安全、防止反射和序列化破坏单例,是最佳实践之一。