单例模式

保证了一个类只有一个实例,并且提供了一个全局访问点。单例模式的主要作用是节省公共资源,方便控制,避免多个实例造成的问题。

实现单例模式的三点:

  • 私有构造函数

  • 私有静态变量维护对象实例

  • 公有静态方法提供获取实例对象

七种单例模式实现

1.静态类:第一次运行初始化,全局使用

2.懒汉模式(线程不安全):懒汉模式是指在第一次获取实例时才创建对象,实现了延迟加载,构造函数返回当前对象实例,但多个访问者同时获取对象实例,就会有多个同样的实例并存,不满足单例

public class LazySingleton {// 私有化构造器private LazySingleton() {}// 定义一个静态变量存储唯一的LazySingleton对象private static LazySingleton instance = null;// 提供一个公共的静态方法获取LazySingleton对象public static LazySingleton getInstance() {// 如果instance为空,则创建一个新的对象if (instance == null) {instance = new LazySingleton();}// 返回instance对象return instance;}
}

懒汉模式(线程安全):加锁,保证线程安全,但是锁占用,导致资源浪费

3.饿汉模式(线程安全):与第一种方式基本一致,在程序启动时直接运行加载,但是可能造成资源浪费。

public class HungrySingleton {// 私有化构造器private HungrySingleton() {}// 定义一个静态变量存储唯一的HungrySingleton对象,并且直接初始化private static HungrySingleton instance = new HungrySingleton();// 提供一个公共的静态方法获取HungrySingleton对象public static HungrySingleton getInstance() {// 直接返回instance对象return instance;}
}

4.使用类的静态内部类:既保证线程安全,又保证懒汉模式,没有加锁影响性能(推荐)

public class SingletonInnerStatic {private static class SingletonHoler {// 静态初始化器,由JVM来保证线程安全private static SingletonInnerStatic instance = new SingletonInnerStatic();}// 私有构造函数private SingletonInnerStatic() {}// 获取对象实例的方法public static SingletonInnerStatic getInstance() {return SingletonHoler.instance;}
}

5.双重锁校验:

public class SingletonDoubleCheck {// 类引用private static volatile SingletonDoubleCheck singletonDoubleCheck;// 构造函数私有化private SingletonDoubleCheck() {}// 双重校验 + 锁实现单例public static SingletonDoubleCheck getInstance() {// 第一次校验是否为nullif (singletonDoubleCheck == null) {// 不为空则加锁synchronized (SingletonDoubleCheck.class) {// 第二次校验是否为nullif (singletonDoubleCheck == null) {singletonDoubleCheck = new SingletonDoubleCheck();}}}return singletonDoubleCheck;}
}

第一次校验是否为null:

主要是为了实现返回单例,避免多余的加锁操作,以及锁的等待和竞争,如果条件不成立就说明已经生成实例,直接返回即可,提高程序执行的效率。

第二次校验是否为null:

第二次校验是关键,这里防止了多线程创建多个实例(一般为两个),这里的特殊情况是这样的:在未创建实例的情况下,A线程和B线程都通过了第一次校验(singletonDoubleCheck为空),这时如果通过竞争B线程拿到了锁就会执行一次new操作,生成一个实例,然后B执行完了A就会拿到资源的锁,如果没有第二次判断的话,这时A线程也会执行一次new操作,这里就出现了第二个类实例,违背了单例原则。所以说两次校验都是必不可少的

提一下上述代码中类引用中的volatile关键字是不能少的:

常见的,该关键字能够实现变量在内存中的可见性(告诉JVM在使用该关键字修饰的变量时在内存中取值,而不是用特定内存区域的副本,因为真实的值可能已经被修改过了),它的另外一种作用是防止JVM对指令进行重排。

其实,在new一个对象的时候会有如下步骤(指令):

1. 分配内存空间
2. 初始化引用
3. 将引用指向内存空间

正常的逻辑肯定以为是这样执行的 1 -> 2 -> 3,但是偏偏JVM拥有指令重排的能力,所以说执行顺序是随机的,可能是 1 -> 3 -> 2,这样的话在多线程环境下可能会拿到空引用:线程A先执行了1,3步骤,紧接着线程B执行getInstance,发现不为null(这里的==是判断实际的值,即引用指向的内存空间),就会返回引用,然而此时引用未初始化。所以说volatile在这里保证指令的执行顺序,在多线程情况下不可少。

6.AtomicReference是一个支持原子操作的对象引用变量,它可以利用CAS(比较并交换)技术来保证线程安全和高效性。一个基本的AtomicReference单例的代码示例如下:

import java.util.concurrent.atomic.AtomicReference;public class Singleton {private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();private Singleton() {}public static Singleton getInstance() {for (;;) {Singleton singleton = INSTANCE.get();if (singleton != null) {return singleton;}singleton = new Singleton();if (INSTANCE.compareAndSet(null, singleton)) {return singleton;}}}
}

要使用这个单例,只需调用Singleton.getInstance()即可。

7.枚举类实现单例模式是一种简洁、安全、有效的方法,它可以防止反射和序列化攻击,保证线程安全和唯一性。一个基本的枚举单例的代码示例如下:

public enum Singleton {INSTANCE;public void doSomething() {// ...}
}

要使用这个单例,只需调用Singleton.INSTANCE.doSomething()即可。

测试:

有以下几种方法可以测试单例的有效性,即是否能保证在多线程环境下,只有一个对象实例被创建和返回。:

  • 使用反射机制,尝试创建多个单例对象,检查它们的内存地址是否相同。
  • 使用序列化和反序列化机制,尝试创建多个单例对象,检查它们的内存地址是否相同。
  • 使用多线程并发调用getInstance()方法,检查返回的对象是否都是同一个实例。
  • 使用断言或者日志打印等方式,验证getInstance()方法返回的对象是否符合预期。

安全:

三种攻击方式:

  • 反射攻击:利用jdk反射API,修改单例类构造函数的访问权限,然后调用构造函数;
  • 序列化攻击:将单例对象实例以字节流的方式写入到文件中,然后再读取文件字节流,反序列化生成对象实例;
  • 调用对象的克隆方法。
public class SingletonAtack {public static void main(String[] args) throws Exception {//正常单例对象Singleton single1 = Singleton.getInstance();//1. 反射攻击Class clazz = Singleton.class;Constructor cons = clazz.getDeclaredConstructor(null);cons.setAccessible(true);Singleton single2 = (Singleton) cons.newInstance(null);//2. 序列化攻击FileOutputStream fos = new FileOutputStream("a.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(single1);oos.flush();oos.close();FileInputStream fis = new FileInputStream("a.txt");ObjectInputStream ois = new ObjectInputStream(fis);Singleton single3 = (Singleton) ois.readObject();//3. clone攻击Singleton single4 = (Singleton) single1.clone();}
}

防止攻击代码示例:

  • 防止反射攻击:在单例类的构造函数中添加判断逻辑,如果已经存在实例对象,就抛出异常。例如:
public class Singleton {private static volatile Singleton instance;private Singleton() {// 防止反射攻击if (instance != null) {throw new RuntimeException("单例模式不允许多个实例");}}public static Singleton getInstance() {if (instance == null) {synchronized(Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
  • 防止序列化攻击:在单例类中实现readResolve方法,返回已有的实例对象。例如:
public class Singleton implements Serializable {private static final long serialVersionUID = 1L;private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized(Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}// 防止序列化攻击private Object readResolve() throws ObjectStreamException {return instance;}
}
  • 防止克隆攻击:在单例类中重写clone方法,返回已有的实例对象。例如:
public class Singleton implements Cloneable{private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized(Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}// 防止克隆攻击@Overrideprotected Object clone() throws CloneNotSupportedException{return instance;}
}

场景:

  • 数据库连接池:为了避免频繁地创建和销毁数据库连接,可以使用单例模式来管理数据库连接池,保证只有一个连接池对象存在。
  • 配置文件读取器:为了提高配置文件的读取效率,可以使用单例模式来缓存配置文件的内容,保证只有一个配置文件读取器对象存在。
  • 日志记录器:为了统一管理日志的输出和格式,可以使用单例模式来创建日志记录器对象,保证只有一个日志记录器对象存在。

单例模式应用示例代码:

  • 日志对象,可以使用java.util.logging.Logger类来创建和获取单例的日志对象。例如:
public class LogTest {// 使用LogTest类的名称作为日志对象的标识符private static final Logger logger = Logger.getLogger(LogTest.class.getName());public static void main(String[] args) {// 使用日志对象记录一条信息级别的消息logger.info("This is a log message");}
}
  • 驱动对象,可以使用java.sql.DriverManager类来注册和获取单例的驱动对象。例如:
public class DriverTest {public static void main(String[] args) throws SQLException {// 注册MySQL驱动DriverManager.registerDriver(new com.mysql.jdbc.Driver());// 获取MySQL驱动实例Driver driver = DriverManager.getDriver("jdbc:mysql://localhost:3306/test");// 使用驱动实例连接数据库Connection conn = driver.connect("jdbc:mysql://localhost:3306/test", null);}
}
  • 缓存对象,可以使用一个静态变量和一个私有构造器来实现单例的缓存对象。例如:
public class Cache {// 创建并初始化一个缓存实例作为静态变量private static final Cache INSTANCE = new Cache();// 创建一个Map用于存储键值对数据private Map<String, Object> data;// 私有化构造器,防止外部创建新的缓存实例private Cache() {data = new HashMap<>();}// 提供一个公共方法用于获取缓存实例public static Cache getInstance() {return INSTANCE;}// 提供一个公共方法用于向缓存中添加数据public void put(String key, Object value) {data.put(key, value);}// 提供一个公共方法用于从缓存中获取数据public Object get(String key) {return data.get(key);}
}
  • 线程池对象,可以使用java.util.concurrent.Executors类来创建和获取单例的线程池对象。例如:
public class ThreadPoolTest {// 创建并初始化一个固定大小为10的线程池作为静态变量 private static final ExecutorService executor = Executors.newFixedThreadPool(10);public static void main(String[] args) {for (int i = 0; i < 20; i++) {// 向线程池提交20个任务,并由线程池分配给空闲线程执行 executor.execute(new Runnable() {@Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } }); } // 关闭线程池,不再接受新任务,并等待已提交任务完成后退出 executor.shutdown(); }
}
  • Runtime对象,可以使用java.lang.Runtime类的getRuntime方法来获取单例的Runtime对象。例如:
public class RuntimeTest { public static void main(String[] args) throws IOException{ // 获取单例的Runtime实例  Runtime runtime = Runtime.getRuntime();  // 使用Runtime实例执行一个命令  Process process = runtime.exec("notepad.exe");  }
}
  • Desktop对象,可以使用java.awt.Desktop类的getDesktop方法来获取单例的Desktop对象。例如:
public class DesktopTest {   public static void main(String[] args) throws IOException{   // 获取单例的Desktop实例   Desktop desktop = Desktop.getDesktop();   // 使用Desktop实例打开一个文件   File file = new File("test.txt");   desktop.open(file);   }
}

Java单例模式详解--七种单例模式实现+单例安全+实际应用场景相关推荐

  1. iOS设计模式 ——单例模式详解以及严格单例模式注意点

    一.我们常用的单例有哪些? [[UIApplication sharedApplication] statusBarStyle];//系统中的单例模式,通过它获取到状态栏的style [NSNotif ...

  2. java单例设计模式_Java设计模式之单例模式详解

    在Java开发过程中,很多场景下都会碰到或要用到单例模式,在设计模式里也是经常作为指导学习的热门模式之一,相信每位开发同事都用到过.我们总是沿着前辈的足迹去做设定好的思路,往往没去探究为何这么做,所以 ...

  3. java connection 单例_Java设计模式之单例模式详解

    Java设计模式之单例模式详解 什么是设计模式 设计模式是在大量的实践中总结和理论之后优选的代码结构,编程风格,以及解决问题的思考方式.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可 ...

  4. 【转】Java 单例模式详解

    概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. ...

  5. Java 单例模式详解(转)

    概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. ...

  6. Java 单例模式详解

    概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. ...

  7. 单例模式应用场景_三、单例模式详解

    4.单例模式详解 4.1.课程目标 1.掌握单例模式的应用场景. 2.掌握IDEA环境下的多线程调试方式. 3.掌握保证线程安全的单例模式策略. 4.掌握反射暴力攻击单例解决方案及原理分析. 5.序列 ...

  8. 以下属于单例模式的优点的是_三、单例模式详解

    4.单例模式详解 4.1.课程目标 1.掌握单例模式的应用场景. 2.掌握IDEA环境下的多线程调试方式. 3.掌握保证线程安全的单例模式策略. 4.掌握反射暴力攻击单例解决方案及原理分析. 5.序列 ...

  9. C++设计模式--单例模式详解(懒汉模式、饿汉模式、双重锁)

    C++设计模式--单例模式详解(懒汉模式.饿汉模式.双重锁) 应用场景 一.单例模式是什么? 二.使用步骤 1.UML图 2.代码实现 应用场景 通常我们在做通讯的时候,我们跟服务器数据交互,假如每次 ...

最新文章

  1. 专访《王者荣耀》美术总监:用6年研究东方美学
  2. java命令行 引用jar包_java命令行引用jar包
  3. 火热报名|5月15日线下沙龙上海站——“大促活动场景下的质量保障”主题
  4. 全向轮机器人左向直线运动分析
  5. 《软件需求十步走》阅读笔记一
  6. mysql无法授权问题
  7. 闲谈IPv6-v4/v6协议转换报文的checksum无关性
  8. SCCM 2007 R7使用手记
  9. 安卓测试常用的 ADB 命令大全,非常全!!!!!
  10. 圈小猫游戏与天使问题——容错值理论
  11. 杭电数字电路课程设计——出租车计费器
  12. 计算机考证要考PS吗
  13. 自己组装电脑后怎么装Win10系统教学
  14. Exploit-exercises
  15. Whois接口查询文档
  16. android 屏幕滚动字幕,LED灯牌显示屏滚动字幕
  17. 简单的VUE购物车应用
  18. buff系统 游戏中_请问BUFF状态
  19. 【控制工程基础】二、方框图化简与梅森公式
  20. Java中File使用--创建文件

热门文章

  1. 高斯移动热源,熔池流场分布,comsol模拟案例
  2. 集十三位资深程序员毕生功力回答:普通程序员如何自学才能进大厂?
  3. 中国人请客吃饭的传统礼仪
  4. C++:MFC+VS2019当你Dlg右键“转到”对话框,提示“未能完成操作,拒绝访问”,资源视图中.re显示“X在另一个编辑器中打开”。
  5. Linux 系统崩溃请随意
  6. 最好的卡尔曼滤波讲解
  7. a全球及中国电子手环行业销售现状及竞争趋势预测报告2022-2027年
  8. sp_WhoIsActive
  9. Windows 中 TCP 端口 139 和 445 的使用
  10. 【华为机试Python3题解】HJ21简单密码