Java23种设计模式系列——创建者模式之单例模式day1-3

181 篇文章 3 订阅
14 篇文章 0 订阅

创建者模式

创建型模式的主要关注点是"怎样创建对象? ”

特点

将对象的创建与使用分离。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节

创建型模式分类

  1. 单例模式
  2. 工厂方法模式
  3. 抽象工程模式
  4. 原型模式
  5. 建造者模式

单例模式

单例模式(Singleton Pattern)是Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

其中Runtime类就使用的单例模式

单例模式的结构

单例模式的主要有以下角色:

  1. 单例类:只能创建一个实例的类
  2. 访问类:使用单例类

单例模式分类

  1. 饿汉式:类加载就会导致该单实例对象被创建(饭一做好,就抢着吃)
  2. 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建(饭做好不急着吃,送到嘴边才吃)

饿汉式实现(静态变量)

即声明就赋值

package builder_pattern.single;

/**
 * 饿汉式(静态变量)
 */
public class HungerByStatic {
    //私有构造器

    public HungerByStatic() {
    }

    //在类中创建本类对象
    private static HungerByStatic instance = new HungerByStatic();

    //提供暴露访问方式
    public static HungerByStatic getInstance(){
        return instance;
    }
}

饿汉式实现(静态代码块)

即在静态代码块中赋值

package builder_pattern.single;

/**
 * 饿汉式(静态代码块)
 */
public class HungerByStaticBlock {

    public HungerByStaticBlock() {
    }

    private static HungerByStaticBlock instence;
    
    static {
        instence = new HungerByStaticBlock();
    }

    public static HungerByStaticBlock getInstance(){
        return instence;
    }
}

饿汉式实现(枚举方式)

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

package builder_pattern.single.hunger;

/**
 * 饿汉式:枚举方式
 */
public enum HungerEnum {
    //实例
    INSTANCE;
}

//使用
HungerEnum instance3 = HungerEnum.INSTANCE;

饿汉式缺点

若该类长时间不使用则会长时间占据内存空间,导致内存浪费!

懒汉式实现(线程不安全)

当多个线程调用时
在这里插入图片描述
可能导致多个线程都同时判断为空而创建多个实例

package builder_pattern.single.lazy;

import java.util.Objects;

/**
 * 懒汉式,线程不安全
 */
public class LazyerThreadNotSafe {
    public LazyerThreadNotSafe() {
    }

    private static LazyerThreadNotSafe instance;

    public static LazyerThreadNotSafe getInstance() {
        //对instance进行判断,未创建则创建,已创建就返回
        if (Objects.isNull(instance)) {
            instance = new LazyerThreadNotSafe();
        }
        return instance;
    }
}

懒汉式实现(线程安全)

使用同步锁关键字:synchronized

package builder_pattern.single.lazy;

import java.util.Objects;

/**
 * 线程安全,单纯就加synchronized
 */
public class LazyerThreadSafe {
    public LazyerThreadSafe() {
    }

    private static LazyerThreadNotSafe instance;

    public static synchronized LazyerThreadNotSafe getInstance() {
        //对instance进行判断,未创建则创建,已创建就返回
        if (Objects.isNull(instance)) {
            instance = new LazyerThreadNotSafe();
        }
        return instance;
    }
}

缺点

使用同步锁会导致性能低下!

懒汉式(双重检查锁)

解决了性能,安全的问题

对同步锁的优化,即调整加锁时机,并且使用关键字volatile确保有序性,否则高并发下会出现空指针异常

package builder_pattern.single.lazy;

import java.util.Objects;

/**
 * 懒汉式:双重检查锁
 */
public class LazyerUseDoubleCheckLock {
    public LazyerUseDoubleCheckLock() {
    }

    //使用关键字volatile确保有序
    private static volatile LazyerUseDoubleCheckLock instance;

    public static LazyerUseDoubleCheckLock getInstance(){
        if (Objects.isNull(instance)){
            synchronized (LazyerUseDoubleCheckLock.class){
                if (Objects.isNull(instance)){
                    instance = new LazyerUseDoubleCheckLock();
                }
            }
        }
        return instance;
    }
}

懒汉式(静态内部类)(最推荐)

利用JVM在加载类时,不会对静态内部类进行加载,而静态内部类由于是静态的所以只会实例化一次并严格遵守实例化顺序

package builder_pattern.single.lazy;

/**
 * 懒汉式:静态内部类
 */
public class LazyerByStatic {
    public LazyerByStatic() {
    }

    private static class staticLazyerHandler{
        //在类中声明并初始化
        private static final LazyerByStatic INSTANCE = new LazyerByStatic();
    }

    //暴露
    public static LazyerByStatic getInstance(){
        return staticLazyerHandler.INSTANCE;
    }
}

破环单例模式

即使用单例而创建多个实例对象(枚举方式无法破坏)

破坏方法

  1. 序列化
  2. 反射

序列化破坏

package builder_pattern.single.destroy;

import builder_pattern.single.hunger.HungerByStatic;
import builder_pattern.single.lazy.LazyerByStatic;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class DestroyUseSerializable {

    //读对象(反序列化)
    public static void reverseSerialization() throws Exception{
        String path = "D:\\tempUse\\HungerByStatic.txt";
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path));
        HungerByStatic o = (HungerByStatic) objectInputStream.readObject();
        System.out.println(o.toString());
        objectInputStream.close();
    }
    //写对象(序列化)
    public static void serialization() throws Exception{
        //获取对象
        HungerByStatic instance = HungerByStatic.getInstance();
        //输出流
        String path = "D:\\tempUse\\"+instance.getClass().getSimpleName()+".txt";
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path));
        objectOutputStream.writeObject(instance);
        objectOutputStream.close();
    }

    public static void main(String[] args) throws Exception {
//        DestroyUseSerializable.serialization();
        DestroyUseSerializable.reverseSerialization();
        DestroyUseSerializable.reverseSerialization();
    }
}

结果

如下图可见,地址不同,说明是两个对象

在这里插入图片描述

解决序列化破坏

添加public Object readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

 public Object readResolve(){
        return instance;
    }

完整代码

package builder_pattern.single.hunger;

import java.io.Serializable;

/**
 * 饿汉式(静态变量)
 */
public class HungerByStatic implements Serializable {
    //私有构造器

    public HungerByStatic() {
    }

    //在类中创建本类对象
    private static HungerByStatic instance = new HungerByStatic();

    //提供暴露访问方式
    public static HungerByStatic getInstance(){
        return instance;
    }


    public Object readResolve(){
        return instance;
    }

}

在这里插入图片描述

反射破坏

package builder_pattern.single.destroy;

import builder_pattern.single.hunger.HungerByStatic;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class DestroyUseReflection {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<HungerByStatic> hungerByStaticClass = HungerByStatic.class;
        Constructor<HungerByStatic> declaredConstructor = hungerByStaticClass.getDeclaredConstructor();
        //取消访问检查
        declaredConstructor.setAccessible(true);
        //创建对象
        HungerByStatic hungerByStatic = declaredConstructor.newInstance();
        HungerByStatic instance = HungerByStatic.getInstance();
        HungerByStatic hungerByStatic1 = declaredConstructor.newInstance();
        System.out.println(hungerByStatic == instance);
        System.out.println(hungerByStatic == hungerByStatic1);
    }
}

结果

两次通过反射创建对象并不是同一个对象
在这里插入图片描述

解决反射破坏

添加构造标识,第一次构造时时false,后续为true,若后续再次构造则抛出异常

private static boolean lock = false;
    //私有构造器
    private HungerByStatic() {
       synchronized (HungerByStatic.class){
           //判断lock,第一次访问为false,否则true
           if (lock){
               throw new RuntimeException("instance has been built");
           }
           lock = true;
       }
    }

完整代码

package builder_pattern.single.hunger;

import java.io.Serializable;

/**
 * 饿汉式(静态变量)
 */
public class HungerByStatic implements Serializable {

    private static boolean lock = false;

    //私有构造器
    private HungerByStatic() {
       synchronized (HungerByStatic.class){
           //判断lock,第一次访问为false,否则true
           if (lock){
               throw new RuntimeException("instance has been built");
           }
           lock = true;
       }
    }

    //在类中创建本类对象
    private static HungerByStatic instance = new HungerByStatic();

    //提供暴露访问方式
    public static HungerByStatic getInstance(){
        return instance;
    }


    public Object readResolve(){
        return instance;
    }

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值