Java实现单例模式之饿汉式、懒汉式、枚举式

作者:我只是一名程序员 时间:2022-12-17 17:50:13 

单例模式的实现(5种)

常用:

饿汉式(线程安全,调用效率高,但是不能延时加载)
懒汉式(线程安全,调用效率不高,可以延时加载)

其他:

双重检测锁式(由于jvm底层内部模型原因,偶尔会出问题,不建立使用)
静态内部类式(线程安全,调用效率高,但是可以延时加载)
枚举单例(线程安全,调用效率高,不能延时加载)
饿汉式单例具体代码如下:


package com.lcx.mode;

/**
*
* 饿汉式单例,不管以后用不用这个对象,我们一开始就创建这个对象的实例,
* 需要的时候就返回已创建好的实例对象,所以比较饥饿,故此叫饿汉式单例。
* @author qq1013985957
*
*/
public class SingletonHanger {
 private static final SingletonHanger instance = new SingletonHanger();
 private SingletonHanger() {
 }
 public static SingletonHanger getInstance(){
   return instance;
 }
}
/**
* 懒汉汉式单例,在需要单例对象的时候,才创建唯一的单例对象,以后再次调用,返回的也是第一创建的单例对象
* 将静态成员初始化为null,在获取单例的时候才创建,故此叫懒汉式。
* @author qq1013985957
*
*/
class SingletonLazy{
 private static SingletonLazy instance = null;
 private SingletonLazy() {
 }
 /**
  * 此方法实现的单例,无法在多线程中使用,多线可以同时进入if方法,会导致生成多个单例对象。
  * @return
  */
 public static SingletonLazy getInstance1(){
   if(instance==null){
     instance = new SingletonLazy();
   }
   return instance;
 }
 /**
  * 大家都会想到同步,可以同步方法实现多线程的单例
  * 但是这种方法不可取,严重影响性能,因为每次去取单例都要检查方法,所以只能用同步代码块的方式实现同步。
  * @return
  */
 public static synchronized SingletonLazy getInstance2(){
   if(instance==null){
     instance = new SingletonLazy();
   }
   return instance;
 }
 /**
  * 用同步代码块的方式,在判断单例是否存在的if方法里使用同步代码块,在同步代码块中再次检查是否单例已经生成,
  * 这也就是网上说的 双重检查加锁的方法
  * @return
  */
 public static synchronized SingletonLazy getInstance3(){
   if(instance==null){
     synchronized (SingletonLazy.class) {
       if(instance==null){
         instance = new SingletonLazy();
       }
     }
   }
   return instance;
 }
}
/**
*
* 使用枚举实现单例模式,也是Effective Java中推荐使用的方式
* 根据具体情况进行实例化,对枚举不熟悉的同学,可以参考我的博客 JAVA 枚举类的初步理解。
* 它的好处:更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使面对复杂的序列和反射攻击。
* @author qq1013985957
*
*/
enum SingletionEnum{
 SingletionEnum("单例的枚举方式");
 private String str ;
 private SingletionEnum(String str){
   this.setStr(str);
 }
 public String getStr() {
   return str;
 }
 public void setStr(String str) {
   this.str = str;
 }

}

以上的单例模式就不测试,大家可以去测试,判断对象的hashcode是否一致来判断是否为同一个对象。

恶汉式、懒汉式的方式还不能防止反射来实现多个实例,通过反射的方式,设置ACcessible.setAccessible方法可以调用私有的构造器,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。

其实这样还不能保证单例,当序列化后,反序列化是还可以创建一个新的实例,在单例类中添加readResolve()方法进行防止。
懒汉汉式单例代码如下:


package com.lcx.mode;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
* 懒汉汉式单例,在需要单例对象的时候,才创建唯一的单例对象,以后再次调用,返回的也是第一创建的单例对象
* 将静态成员初始化为null,在获取单例的时候才创建,故此叫懒汉式。
* @author qq1013985957
*
*/
public class Singleton implements Serializable{
 /**
  *
  */
 private static final long serialVersionUID = -5271537207137321645L;
 private static Singleton instance = null;
 private static int i = 1;
 private Singleton() {
   /**
    * 防止反射攻击,只运行调用一次构造器,第二次抛异常
    */
   if(i==1){
     i++;
   }else{
     throw new RuntimeException("只能调用一次构造函数");
   }
   System.out.println("调用Singleton的私有构造器");

}
 /**
  * 用同步代码块的方式,在判断单例是否存在的if方法里使用同步代码块,在同步代码块中再次检查是否单例已经生成,
  * 这也就是网上说的 双重检查加锁的方法
  * @return
  */
 public static synchronized Singleton getInstance(){
   if(instance==null){
     synchronized (Singleton.class) {
       if(instance==null){
         instance = new Singleton();
       }
     }
   }
   return instance;
 }
 /**
  *
  * 防止反序列生成新的单例对象,这是effective Java 一书中说的用此方法可以防止,具体细节我也不明白
  * @return
  */
 private Object readResolve(){
   return instance;
 }
 public static void main(String[] args) throws Exception {
   test1();
   test2();
 }
 /**
  * 测试 反序列 仍然为单例模式
  * @throws Exception
  */
 public static void test2() throws Exception{
   Singleton s = Singleton.getInstance();
   ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("E:\\Singleton.txt")));
   objectOutputStream.writeObject(s);
   ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("E:\\Singleton.txt")));
   Object readObject = objectInputStream.readObject();
   Singleton s1 = (Singleton)readObject;
   System.out.println("s.hashCode():"+s.hashCode()+",s1.hashCode():"+s1.hashCode());

objectOutputStream.flush();
   objectOutputStream.close();
   objectInputStream.close();
 }
 /**
  * 测试反射攻击
  * @throws Exception
  */
 public static void test1(){
   Singleton s = Singleton.getInstance();
   Class c = Singleton.class;
   Constructor privateConstructor;
   try {
     privateConstructor = c.getDeclaredConstructor();
     privateConstructor.setAccessible(true);
     privateConstructor.newInstance();
   } catch (Exception e) {
     e.printStackTrace();
   }
 }
}

验证反射攻击结果:

Java实现单例模式之饿汉式、懒汉式、枚举式

如果不添加readResolve方法的结果:

Java实现单例模式之饿汉式、懒汉式、枚举式添加readResolve方法的结果:

Java实现单例模式之饿汉式、懒汉式、枚举式

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

标签:java,单例模式
0
投稿

猜你喜欢

  • IDEA使用Gradle构建SpringBoot项目工程的详细教程

    2022-08-31 16:28:26
  • C#实现获取鼠标句柄的方法

    2022-05-06 21:13:41
  • JAVA多线程之实现用户任务排队并预估排队时长

    2022-03-26 03:06:20
  • 详解java 中泛型中的类型擦除和桥方法

    2021-07-10 23:55:08
  • C#实现农历日历的方法

    2022-08-17 21:27:29
  • Java操作pdf的工具类itext的处理方法

    2023-07-14 11:51:53
  • Android自定义加载圈动画效果

    2021-07-20 14:52:23
  • Android简单实现天气预报App

    2022-11-05 05:50:32
  • c#中list.FindAll与for循环的性能对比总结

    2021-08-15 21:20:44
  • Android使用DrawerLayout实现双向侧滑菜单

    2022-02-04 12:50:31
  • Java实现学生选课管理系统

    2023-04-12 20:00:16
  • Java中的Set、List、Map的用法与区别介绍

    2022-10-03 04:11:48
  • Flutter集成高德地图并添加自定义Maker的实践

    2022-07-11 21:32:49
  • Android 登录处理简单实例(源码下载)

    2023-06-22 11:44:43
  • Java之SSM中bean相关知识汇总案例讲解

    2021-11-10 06:16:26
  • 新手初学Java常见排序算法

    2022-05-09 03:35:45
  • java如何给对象按照字符串属性进行排序

    2022-05-23 02:17:14
  • 5种Android数据存储方式汇总

    2023-08-06 06:49:04
  • Spring Cloud微服务架构的构建:分布式配置中心(加密解密功能)

    2021-10-24 05:49:59
  • 新手了解java IO基础知识

    2023-10-20 22:19:45
  • asp之家 软件编程 m.aspxhome.com