单例模式再次学习

一单例模式是什么?

单例模式是指在内存中只会创建且仅创建一次对象的设计模式(注意内部的成员以及方法用private修饰,访问权限仅限于类的内部)

二单例模式

1 饿汉式(一劳永逸)

在类加载时已经创建好该单例对象,等待被程序使用

package com.ma.singleton;

//饿汉式(一劳永逸)
//在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可
public class HungrySingleton {
    private static  HungrySingleton hungrySingleton=new HungrySingleton();

    private HungrySingleton() { }

    public static HungrySingleton getInstance(){
        return  hungrySingleton;
    }
}

在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。

2懒汉式 (临阵磨枪)

在真正需要使用对象时才去创建该单例类对象

package com.ma.singleton;
//懒汉式 (临阵磨枪)
public class lazybonesSingleton {
       private static lazybonesSingleton lazybonesSingleton;
       private lazybonesSingleton() { }
  //程序使用对象前先进行判断 是否已经实例化(判空)如果没实例化则实例化,如果实例化则直接返回
  public static lazybonesSingleton getInstance(){
           if(lazybonesSingleton==null){
                lazybonesSingleton=new lazybonesSingleton();
           }
               return lazybonesSingleton;
       }
}

3 静态内部类

外部类 SingleTon加载时并不需要立即加载内部类SingleTonHoler,内部类不被加载则不去初始化INSTANCE,故而不占内存。能保证单例的唯一性,同时也延迟了单例的实例化。

public class SingleTon{
  private SingleTon(){}
 
  private static class SingleTonHoler{
     private static SingleTon INSTANCE = new SingleTon();
 }
 
  public static SingleTon getInstance(){
    return SingleTonHoler.INSTANCE;
  }
}

4 枚举单例

枚举类不需要考虑关注线程安全、破坏单例和性能问题。
在程序启动时,会调用SingletonEnum的空参构造器,实例化好一个SingletonEnum对象赋给INSTANCE,之后再也不会实例化

package com.ma.singleton;
public enum SingletonEnum {
    INSTANCE;
    //构造方法私有化
    public void anyMethod(){}
}

三单例模式分析

1懒汉式存在的问题

不适用于多线程,在多线程的模式下会创建多个单例对象。
原因是多线程的执行是默认是无序的,多个线程都是会执行到 **if(lazybonesSingleton==null)**当多个线程判断都是为空的时候,就会产生多个单例对象。
在这里插入图片描述
多线程先单例模式失败案例
![在这里插入图片描述](https://img-blog.csdnimg.cn/a14bb8677c5c4d29bc37b102a8de1032.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5pyA5L2z5ZCs5LyXIQ==,size_20,color_FFFFFF,t_70,g_se,x_16
生成多个单例对象

2解决多线程懒汉式失败的方法

2.1 DCL懒汉式

因为是多线程的情况下产生的,所以使用上锁的的方式去解决这个问题。
采用DCL懒汉式:Double Check(双重校验) + Lock(加锁)

        package com.ma.singleton;
//懒汉式 (临阵磨枪)
public class LazybonesSingleton {
       private static LazybonesSingleton lazybonesSingleton;
       private LazybonesSingleton() {
           System.out.println(Thread.currentThread().getName()+"创造LazybonesSingleton");
       }
  //程序使用对象前先进行判断 是否已经实例化(判空)如果没实例化则实例化,如果实例化则直接返回
  public static LazybonesSingleton getInstance(){
      if(lazybonesSingleton==null){// 多个线程同时看到lazybonesSingleton = null,第一个线程锁了资源
          // 如果不为null,则直接返回lazybonesSingleton
          synchronized (LazybonesSingleton.class){//上锁
              if(lazybonesSingleton==null){//只会有一个线程进入 实例化对象
                  lazybonesSingleton=new LazybonesSingleton();
              }
          }
      }
               return lazybonesSingleton;
       }
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                LazybonesSingleton.getInstance();
            }).start();
        }
    }
}

在这里插入图片描述

2.2 加volatile防止指令重排

volatile的三大作用
1保证可见性
2 不保证原子性
3禁止指令重排(这里使用到)
使用volatile关键字修饰的变量,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变换。
当创建一个对象,在JVM中会经过三步:
(1)为singleton分配内存空间
(2)初始化singleton对象
(3)将singleton指向分配好的内存空间
而不加volatile则会导致2 、3 步的顺序发生颠倒,从而导致别的线程判断lazybonesSingleton已经不为空,但是确返回不了lazybonesSingleton对象
在这里插入图片描述

四破坏单例模式

4.1通过反射去破解单例

反射可破解前三种单例模式,但是不能破解枚举。
反射机制可以调用该私有构造器来创建对象。即便是在构造其中设置“防止多次实例化的代码”(比如设置一个flag)也于事无补,因为任然可以通过反射机制来修改flag,从而达到多次实例化的目的。在这里插入图片描述

4.2 通过序列化去破解单例

前提是单例模式上实现接口Serializable,不实现接口的话,会出现 java.io.NotSerializableException的异常
在这里插入图片描述

代码案例

public class SingTest {
    public static void main(String[] args) throws Exception {
        //序列化
        // 创建输出流
             ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("test.file"));
        // 将单例对象写到文件中
        oos.writeObject(LazybonesSingleton.getInstance());
        oos.close();
        //反序列化
        // 从文件中读取单例对象
        File file=new File("test.file");
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
        LazybonesSingleton instance=(LazybonesSingleton) ois.readObject();
        // 判断是否是同一个对象
        System.out.println(instance);
        System.out.println(LazybonesSingleton.getInstance());
          ois.close();
    }
  }

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值