面向对象设计模式--创建模式--单例模式singleton

单例模式:

   作用:保证了系统中 该类只存在一个对象,节省了系统资源的浪费,对于一些想要频繁 创建和销毁的对象, 使用单例模式即可提升系统性能

   注意:当实例化一个单例类时,必须使用相应的方法创建 而不是使用new, new构造化实例都是多例的

   使用场景:
       频繁创建销毁的对象,创建对象耗时过多或耗资源多(即重量级对象),但又经常用到的对象,工具类对象、频繁访问数据库或者文件对象(比如,数据源、session工厂)

   实例: jdk中 的java.lang.Runtime 就是经典的单例模式

   原理:采取一定方法,保证整个软件系统中,对某个类只存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

   懒汉式是,只有用到这个用例的时候,再将他实例化,不会浪费,所以效率要高一些。
   饿汉式则是程序只要开始,就将他实例化,到用到他的时候就省去了再实例的时间,所以速度和反应快。这是这俩的区别

常见的几种单例模式有:懒汉式,饿汉式,枚举,静态内部类实现等,而懒汉式最重要的是双重检测锁模式的懒汉式单例称为DCL懒汉式(下面的例子有对DCL式进行原理分析)

单例模式的UML类图

 角色分析:

单例 (Singleton) 类声明了一个名为 get­Instance获取实例的静态方法来返回其所属类的一个相同实例。

单例的构造函数必须对客户端 (Client) 代码隐藏。 调用 获取实例方法必须是获取单例对象的唯一方式。

下面进行举例

饿汉式写法一:

package com.creational.singleton;

/**
 * @author jdw
 * @date 2021/8/18-11:45
 */
//饿汉式--单例   一上来就new 对象占据空间

/**
 * 1.构造器私有化(防止外部new)
 * 2.类的内部创建对象
 * 3.向外暴露一个静态的公共方法。
 * 4.实现代码
 */
public class Hungry {
    private Hungry() {  //构造器私有,别人无法new 此对象,保证单例

    }

    private final static Hungry hungry = new Hungry();   //添加static保证实例

    public static Hungry getInstance() {   //获取实例
        return hungry;
    }

    public static void main(String[] args) {
        //测试
        Hungry instance = Hungry.getInstance();
        Hungry instance2 = Hungry.getInstance();
        System.out.println(instance == instance2);
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
    }
/**
 * 优点:简单,类加载时候完成实例化。避免线程同步问题
 * 缺点:类加载时候完成实例化,没达到lazy loading效果, 存在资源浪费
 * 这种方式基于 classloder(类装载)机制 避免了多线程的同步 ,不过  instance 在类转载就实例化 ,导致类转载的情况很多
 * 在单例模式中 大都是调用getInstance方法装载, 不能确定有其他方式 (或其他静态方法)导致类装载 这时候初始化 instance
 * 没达到懒加载效果
 */
}

饿汉式写法二:

package com.creational.singleton;

/**
 * @author jdw
 * @date 2021/8/18-11:45
 */
//饿汉式--单例   一上来就new 对象占据空间

/**
 * 1.构造器私有化(防止外部new)
 * 2.类的内部创建对象
 * 3.向外暴露一个静态的公共方法。
 * 4.实现代码
 */
public class Hungry2 {
    private Hungry2() {  //构造器私有,别人无法new 此对象,保证单例
    }

    private  static Hungry2  hungry;   //final是定义常量 ,必须要初始化,不让创建无法赋值. 修饰方法不可重写,修饰类不可继承

    static {   //在静态代码块创建单例对象
        hungry = new Hungry2();
    }

    public static Hungry2 getInstance() {   //获取实例
        return hungry;
    }

    public static void main(String[] args) {
        //测试
        Hungry2 instance = Hungry2.getInstance();
        Hungry2 instance2 = Hungry2.getInstance();
        System.out.println(instance == instance2);
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
    }
/**
 * 和上一种方法类似,将类的实例化放在了静态代码块中,只是在类装载时候,执行了静态代码块中的代码,初始化类的实例。优缺点一样。
 */
}

懒汉式写法一:

package com.creational.singleton;

/**
 * @author jdw
 * @date 2021/8/18-11:52
 */
//懒汉式-单例
    //提供一个静态的共有方法,使用到该方法时候采取 创建实例
public class LazyMan {
    private LazyMan(){  //构造器私有
        System.out.println(Thread.currentThread().getName()+"--ok");
    }

    private volatile static LazyMan lazyMan;

    //双重检测锁模式的 懒汉式单例 DCL(Double-Check) 懒汉式   两次判断 是否有被实例化
    public static LazyMan getInstance(){
        if(lazyMan==null){   //等于空说明没创建
            synchronized (LazyMan.class){ //LazyMan  加锁 只有一个线程可以进入
                if(lazyMan==null){
                    lazyMan = new LazyMan();
                    /**
                     * 问题 : 不是原子性操作
                     * 原理:
                     * new一个对象发生了:
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把对象指向这个空间
                     *期待执行顺序123
                     *
                     * 实际可能是: 123 或 132
                     *          A线程执行132  先分配空间 ,把一个空对象指向这个空间,而还没执行 2 的时候
                     *          B线程来了 ,
                     *
                     *  解决: 使用 volatile 关键字 保证 数据的 可见性  有序性 避免指令重排 不保证原子性
                     *  volatile  加了 volatile 其他线程可以相互看到 变量  volatile变量可以被看作是一种  程度较轻的 synchronized
                     */
                }
            }
        }

        return lazyMan;
    }
    //------------------单线程下 单例ok

    //多线程并发 不安全
  //原因:当A线程进入,lazyMan等于null的 进行new操作,B线程也进入,A还没创建完成 lazyMan 依然是 null 然后B也创建 就有两个实例了不满足单例模型
   //解决; 双重检测锁模式
    //
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                System.out.println(LazyMan.getInstance());
            }).start();
        }
    }
    /**
     * 优点:实现了懒加载,保证线程 安全
     * *Double-Check 多线程常用方法 ,进行两次if 检测 保证线程安全
     * 实例化代码只需要执行一次 ,后面在访问 通过 if 不为空 直接 返回第一次创建 的 实例化,避免反复同步操作
     * 线程安全:延迟加载(用时创建),效率高
     * 推荐
     */
}

懒汉式写法二

package com.creational.singleton;

/**
 * @author jdw
 * @date 2021/8/18-11:52
 */
//懒汉式-单例
    //提供一个静态的共有方法,使用到该方法时候采取 创建实例
public class LazyMan2 {
    private LazyMan2(){  //构造器私有
        System.out.println(Thread.currentThread().getName()+"--ok");
    }

    private volatile static LazyMan2 lazyMan;

    //双重检测锁模式的 懒汉式单例 DCL 懒汉式
    public static synchronized LazyMan2 getInstance(){
        if(lazyMan==null){   //等于空说明没创建
            lazyMan = new LazyMan2();
        }
        return lazyMan;
    }
    //------------------单线程下 单例ok

    //多线程并发 不安全
  //原因:当A线程进入,lazyMan等于null的 进行new操作,B线程也进入,A还没创建完成 lazyMan 依然是 null 然后B也创建 就有两个实例了不满足单例模型
   //解决; 需要加锁
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                System.out.println(LazyMan2.getInstance());
            }).start();
        }
    }
    /**
     * 优点:实现了懒加载,
     * 缺点:效率太低了,每个线程想要获得类的实例化时,在执行getInstance方法 都要进行同步操作。
     * 而实际上,这个方法执行一次实例化就够了,后面的想获得实例化,直接return 就行了。
     * 而在多线程环境下依然存在线程不安全问题。在实际开发中 不推荐
     */
}

使用静态内部类实现单例

package com.creational.singleton;

/**
 * @author jdw
 * @date 2021/8/18-17:29
 */
//静态内部类实现 单例线程安全 懒加载
public class StaticInClass {

    //原理:当外部类 StaticInClass 在被加载时候 内部类 SingletonInstance 不会加载
    //只有调用了内部类 里面 的方法 或者 想要使用里面的 属性 才会被加载

    private static class SingletonInstance {  //静态内部类
        private static final StaticInClass instance = new StaticInClass();
    }

    //提供一个 静态共有 方法 ,直接返回 实例
    public static StaticInClass getInstance() {  //加载静态类是 线程安全的 不想要加 锁
        return SingletonInstance.instance;
    }


    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(StaticInClass.getInstance());
            }).start();

        }
    }
/**
 * 优点:实现了懒加载,保证线程安全 效率高 推荐
 * 采用类的装载机制来保证实例化时 只有一个线程
 * 类的静态属性只会在 第一次加载类时候初始化 , jvm(java虚拟机) 帮助我们保证了线程安全,在类的初始化时,别的线程无法进入
 *
 */
}

枚举

package com.creational.singleton;

/**
 * @author jdw
 * @date 2021/8/18-17:46
 */
public class Enumerate {

    enum Singleton{  //定义枚举实现单例
        instance; //属性
        public void sayOK(){
            System.out.println("OK~");
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                System.out.println(Singleton.instance.hashCode());
            }).start();
        }
    }
    /**
     * 优点: 利用jdk1.5添加的枚举 实现单例模式。避免了多线程同步问题,而且还可以防止序列化 重新创建新的 对象
     * 推荐
     */
}

总结:

  1. 在类中添加一个私有静态成员变量用于保存单例实例。

  2. 声明一个公有静态构建方法用于获取单例实例。

  3. 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。

  4. 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。

  5. 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值