Java单例模式

简介: 单例模式应该是我们接触的众多设计模式中的第一个,但是对于单例模式的一些细节地方对于初学者来说并不是很清楚,所以本文就来整理下单例模式。


 单例模式应该是我们接触的众多设计模式中的第一个,但是对于单例模式的一些细节地方对于初学者来说并不是很清楚,所以本文就来整理下单例模式。

单例模式

 单例模式的核心是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。

单例的使用场景

   Spring中bean对象的模式实现方式

   servlet中每个servlet的实例

   spring mvc和struts1框架中,控制器对象是单例模式

   应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加

   操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统

   项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。

   数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源

   …

单例模式优点

   由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决

   单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理

单例的实现方式

image.png

饿汉式

 也就是类加载的时候立即实例化对象,实现的步骤是先私有化构造方法,对外提供唯一的静态入口方法,实现如下

/**
 * 单例模式:饿汉式
 * @author 波波烤鸭
 *
 */
public class SingletonInstance1 {
  // 声明此类型的变量,并实例化,当该类被加载的时候就完成了实例化并保存在了内存中
  private static SingletonInstance1 instance = new SingletonInstance1();
  // 私有化所有的构造方法,防止直接通过new关键字实例化
  private SingletonInstance1(){}
  // 对外提供一个获取实例的静态方法
  public static SingletonInstance1 getInstance(){
    return instance;
  }
}

 饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字

问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!

懒汉式

/**
 * 单例模式:懒汉式
 * @author 波波烤鸭
 *
 */
public class SingletonInstance2 {
  // 声明此类型的变量,但没有实例化
  private static SingletonInstance2 instance = null;
  // 私有化所有的构造方法,防止直接通过new关键字实例化
  private SingletonInstance2(){}
  // 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字
  public static synchronized SingletonInstance2 getInstance(){
    if(instance == null){
      // 当instance不为空的时候才实例化
      instance = new SingletonInstance2();
    }
    return instance;
  }
}

 此种方式在类加载后如果我们一直没有调用getInstance方法,那么就不会实例化对象。实现了延迟加载,但是因为在方法上添加了synchronized关键字,每次调用getInstance方法都会同步,所以对性能的影响比较大。

双重检测锁式

/**
 * 单例模式:懒汉式
 * 双重检测机制
 * @author 波波烤鸭
 *
 */
public class SingletonInstance3 {
  // 声明此类型的变量,但没有实例化
  private static SingletonInstance3 instance = null;
  // 私有化所有的构造方法,防止直接通过new关键字实例化
  private SingletonInstance3(){}
  // 对外提供一个获取实例的静态方法,
  public static  SingletonInstance3 getInstance(){
    if(instance == null){
      SingletonInstance3 s3 = null;
      synchronized(SingletonInstance3.class){
        s3 = instance;
        if(s3 == null){
          synchronized(SingletonInstance3.class){
            if(s3 == null){
              s3 = new SingletonInstance3();
            }
          }
        }
        instance = s3;
      }
    }
    return instance;
  }
}

 这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。

问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。

静态内部类式

/**
 * 静态内部类实现方式
 * @author 波波烤鸭
 *
 */
public class SingletonInstance4 {
  // 静态内部类
  public static class SingletonClassInstance{
    // 声明外部类型的静态常量
    public static final SingletonInstance4 instance = new SingletonInstance4();
  }
  // 私有化构造方法
  private SingletonInstance4(){}
  // 对外提供的唯一获取实例的方法
  public static SingletonInstance4 getInstance(){
    return SingletonClassInstance.instance;
  }
}

注意点:

   外部类没有static属性,则不会像饿汉式那样立即加载对象。

   只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.

   兼备了并发高效调用和延迟加载的优势!– 兼备了并发高效调用和延迟加载的优势!

枚举单例

/**
 * 单例模式:枚举方式实现
 * @author dengp
 *
 */
public enum SingletonInstance5 {
  // 定义一个枚举元素,则这个元素就代表了SingletonInstance5的实例
  INSTANCE;
  public void singletonOperation(){
    // 功能处理
  }
}

测试代码

public static void main(String[] args) {
  SingletonInstance5 s1  = SingletonInstance5.INSTANCE;
  SingletonInstance5 s2  = SingletonInstance5.INSTANCE;
  System.out.println(s1 == s2); // 输出的是 true
}

优点:

   实现简单

   枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!

缺点:

   无延迟加载

单例模式漏洞

   通过反射的方式我们依然可用获取多个实例(除了枚举的方式)

public static void main(String[] args) throws Exception, IllegalAccessException {
  SingletonInstance1 s1 = SingletonInstance1.getInstance();
  // 反射方式获取实例
  Class c1 = SingletonInstance1.class;
  Constructor constructor = c1.getDeclaredConstructor(null);
  constructor.setAccessible(true);
  SingletonInstance1 s2 = (SingletonInstance1)constructor.newInstance(null);
  System.out.println(s1);
  System.out.println(s2);
}

输出结果:

com.dpb.single.SingletonInstance1@15db9742
com.dpb.single.SingletonInstance1@6d06d69c

产生了两个对象,和单例的设计初衷违背了。

解决的方式是在无参构造方法中手动抛出异常控制

// 私有化所有的构造方法,防止直接通过new关键字实例化
private SingletonInstance2(){
  if(instance != null){
    // 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化
    throw new RuntimeException("单例模式只能创建一个对象");
  }
}

image.png

   通过反序列化的方式也可以破解上面几种方式(除了枚举的方式)

public static void main(String[] args) throws Exception, IllegalAccessException {
  SingletonInstance2 s1 = SingletonInstance2.getInstance();
  // 将实例对象序列化到文件中
  ObjectOutputStream oos = new ObjectOutputStream(
      new FileOutputStream("c:/tools/a.txt"));
  oos.writeObject(s1);
  oos.flush();
  oos.close();
  // 将实例从文件中反序列化出来
  ObjectInputStream ois = new ObjectInputStream(
      new FileInputStream("c:/tools/a.txt"));
  SingletonInstance2 s2 = (SingletonInstance2) ois.readObject();
  ois.close();
  System.out.println(s1);
  System.out.println(s2);
}

输出结果:

com.dpb.single.SingletonInstance2@5c647e05
com.dpb.single.SingletonInstance2@4c873330

是两个不同的对象,同样破坏了单例模式,这种情况怎么解决呢

我们只需要在单例类中重写readResolve方法并在该方法中返回单例对象即可,如下:

package com.dpb.single;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
 * 单例模式:懒汉式
 * @author 波波烤鸭
 *
 */
public class SingletonInstance2 implements Serializable{
  // 声明此类型的变量,但没有实例化
  private static SingletonInstance2 instance = null;
  // 私有化所有的构造方法,防止直接通过new关键字实例化
  private SingletonInstance2(){
    if(instance != null){
      // 只能有一个实例存在,如果再次调用该构造方法就抛出异常,防止反射方式实例化
      throw new RuntimeException("单例模式只能创建一个对象");
    }
  }
  // 对外提供一个获取实例的静态方法,为了数据安全添加synchronized关键字
  public static synchronized SingletonInstance2 getInstance(){
    if(instance == null){
      // 当instance不为空的时候才实例化
      instance = new SingletonInstance2();
    }
    return instance;
  }
  // 重写该方法,防止序列化和反序列化获取实例
  private Object readResolve() throws ObjectStreamException{
    return instance;
  }
}

image.png

说明:readResolve方法是基于回调的,反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!

常见的五种单例模式在多线程环境下的效率测试

大家只要关注相对值即可。在不同的环境下不同的程序测得值完全不一样

image.png

总结

这几种设计模式如何选择

1、单例对象占用资源少,不需要延时加载:

枚举式 好于 饿汉式

2、单例对象占用资源大,需要延时加载:

静态内部类式 好于 懒汉式


相关文章
|
19天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
26 2
|
29天前
|
设计模式 安全 Java
Java编程中的单例模式深入剖析
【10月更文挑战第21天】在Java的世界里,单例模式是设计模式中一个常见而又强大的存在。它确保了一个类只有一个实例,并提供一个全局访问点。本文将深入探讨如何正确实现单例模式,包括常见的实现方式、优缺点分析以及最佳实践,同时也会通过实际代码示例来加深理解。无论你是Java新手还是资深开发者,这篇文章都将为你提供宝贵的见解和技巧。
95 65
|
19天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
26天前
|
设计模式 SQL 安全
Java编程中的单例模式深入解析
【10月更文挑战第24天】在软件工程中,单例模式是设计模式的一种,它确保一个类只有一个实例,并提供一个全局访问点。本文将探讨如何在Java中使用单例模式,并分析其优缺点以及适用场景。
13 0
|
30天前
|
SQL 设计模式 Java
[Java]单例模式
本文介绍了单例模式的概念及其实现方式,包括饿汉式和懒汉式两种形式,并详细探讨了懒汉式中可能出现的线程安全问题及其解决方案,如锁方法、锁代码块和双重检查锁(DCL)。文章通过示例代码帮助读者更好地理解和应用单例模式。
29 0
|
2月前
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
42 11
|
1月前
|
设计模式 SQL 安全
【编程进阶知识】Java单例模式深度解析:饿汉式与懒汉式实现技巧
本文深入解析了Java单例模式中的饿汉式和懒汉式实现方法,包括它们的特点、实现代码和适用场景。通过静态常量、枚举类、静态代码块等方式实现饿汉式,通过非线程安全、同步方法、同步代码块、双重检查锁定和静态内部类等方式实现懒汉式。文章还对比了各种实现方式的优缺点,帮助读者在实际项目中做出更好的设计决策。
43 0
|
3月前
|
设计模式 存储 负载均衡
【五】设计模式~~~创建型模式~~~单例模式(Java)
文章详细介绍了单例模式(Singleton Pattern),这是一种确保一个类只有一个实例,并提供全局访问点的设计模式。文中通过Windows任务管理器的例子阐述了单例模式的动机,解释了如何通过私有构造函数、静态私有成员变量和公有静态方法实现单例模式。接着,通过负载均衡器的案例展示了单例模式的应用,并讨论了单例模式的优点、缺点以及适用场景。最后,文章还探讨了饿汉式和懒汉式单例的实现方式及其比较。
【五】设计模式~~~创建型模式~~~单例模式(Java)
|
2月前
|
设计模式 Java 安全
Java设计模式-单例模式(2)
Java设计模式-单例模式(2)
|
3月前
|
设计模式 安全 Java
Java 单例模式,背后有着何种不为人知的秘密?开启探索之旅,寻找答案!
【8月更文挑战第30天】单例模式确保一个类只有一个实例并提供全局访问点,适用于需全局共享的宝贵资源如数据库连接池、日志记录器等。Java中有多种单例模式实现,包括饿汉式、懒汉式、同步方法和双重检查锁定。饿汉式在类加载时创建实例,懒汉式则在首次调用时创建,后者在多线程环境下需使用同步机制保证线程安全。单例模式有助于提高代码的可维护性和扩展性,应根据需求选择合适实现方式。
35 1
下一篇
无影云桌面