首页 > 其他 > 详细

单例模式

时间:2021-04-11 16:28:47      阅读:22      评论:0      收藏:0      [点我收藏+]

一、单例模式介绍

? 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

? 我们JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)

? 优点:单例模式节省公共资源,单例模式方便控制

? 实现:

? 构造器私有化:如果要保证一个类不被多次实例化,就要阻止不能被new出来,私有就把所以构造器私有化。

? 类的内部创建对象:只对类进行一次实例化,以后都使用这一个实例化对象

? 以静态方法返回实例:因为外界不能new对象,所以我们需要提供一个公共的方法让外界拿到实例化对象。

二、单例设计模式的方式

1) 饿汉式

饿汉式:先把对象创建好,要用时直接拿来用。

饿汉式虽然简单好用,但是却可能造成资源浪费,因为对象都是提前创建好的,我们却不一定会使用

package com.xin.singleton;

/*
    单例模式:饿汉式(静态常量)
        构造器私有化
        类的内部创建对象
        对外暴露一个静态公共方法
 */
public class Singleton {
    //构造器私有,防止直接new
    private Singleton() {}

    //提供一个静态类型的Singleton01,先创建好实例
    private static final Singleton instance = new Singleton();

    //给一个公共方法,让外部获取实例
    public static Singleton getInstance() {
        return instance;
    }
}

/*
	写法简单,类装载的时候完成了初始化,避免了线程同步问题
	没有达到 lazy loading(懒加载)效果,不使用就会造成内存资源浪费
*/
package com.xin.singleton;

/*
    单例模式:饿汉式(静态代码块)
        构造器私有化
        类的内部创建对象
        对外暴露一个静态公共方法
 */
public class Singleton {
    //构造器私有,防止直接new
    private Singleton() {}

    //提供一个静态类型的Singleton01,先创建好实例
    private static final Singleton instance;
    //静态代码块中创建单例对象
    static {
        instance = new Singleton();
    }

    //给一个公共方法,让外部获取实例
    public static Singleton getInstance() {
        return instance;
    }
}

/*
	这种方式和静态常量方法类似,只是把实例化过程放到了静态代码块中,优缺点都一样
	这种单例模式可用,但是也可能造成内存浪费
*/

2) 懒汉式

因为饿汉式会造成内存浪费,所以就有了懒汉式

懒汉式就是不先创建对象实例,等我们需要用的时候再创建

package com.xin.singleton;

/*
    单例模式:懒汉式(线程不安全)
 */
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    //提供一个静态实例方法,调用该方法时才去创建实例对象
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

/*
	懒汉模式解决了饿汉模式可能引起的资源浪费问题,起到了懒加载效果,但是这种模式
	在并发情况下会出现创建多个对象的情况,只适用于单线程模式,并发下并不安全
	实际开发中,不使用这种情况
*/
package com.xin.singleton;

/*
    单例模式:懒汉式(线程安全,同步方法)
 */
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    //提供一个静态实例方法,调用该方法时才去创建实例对象
    //加入同步处理代码,保证线程安全
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

/*
	这种情况保证了线程的安全,但是每一次调用方法时都要进行同步,大大的降低了效率,
	而事实上这个方法执行一次就够了,后面调用return就可,开发中不推荐使用
*/

3) 双重检查加锁(DCL)

Double-Check概念是多线程开发中常使用到的,DCL是完美的解决了单例模式中性能和资源浪费的问题

package com.xin.singleton;

/*
    单例模式:懒汉式(双检锁)
 */
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    //提供一个静态实例方法,调用该方法时才去创建实例对象
    //加入同步处理代码,保证线程安全
    public static synchronized Singleton getInstance() {
        //先验证是否创建对象
        if (instance == null) {
            //只有对象为创建才上锁
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

/*
	但是DCL在并发情下也会存在一个问题,因为jvm指令是乱序的
	对象实例化会有三个指令:
指令1:分配对象内存。

指令2:调用构造器,初始化对象属性。

指令3:构建对象引用指向内存。

而编译器会将指令进行优化,优化后顺序会变成:

1、执行指令1:分配对象内存,

2、执行指令3:构建对象引用指向内存。

3、执行指令2:  调用构造器,初始化对象属性。

		而这样子就可能会发生一种情况:当线程1进行到第二步的时候,还没有初始化结果,这个时候cpu切换到了线程2,线程而就会走第一个判断,会发现实例不为空,进而直接返回,造成了线程2拿到了一个空的未初始化的对象。DCL 就失效了。
*/

解决指令重排问题,避免DCL失效:

? 使用 volatile 关键字

package com.xin.singleton;

/*
    单例模式:懒汉式(双检锁)
 */
public class Singleton {
    //使用volatile关键字,保证可见性,避免发生指令重排序
    private static volatile Singleton instance;

    private Singleton() {}

    //提供一个静态实例方法,调用该方法时才去创建实例对象
    //加入同步处理代码,保证线程安全
    public static synchronized Singleton getInstance() {
        //先验证是否创建对象
        if (instance == null) {
            //只有对象为创建才上锁
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

/*
	双检测锁定的方式 是只有当对象未创建的时候才对请求加锁,对象创建以后都不会上锁,
	这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),
	直接return实例化对象,也避免的反复进行方法同步.线程安全;延迟加载;效率较高
	DCL是完美的解决了单例模式中性能和资源浪费的问题
	推荐使用
*/

4) 静态内部类

package com.xin.singleton;

/*
    单例模式:静态内部类
 */
public class Singleton {

    private Singleton() {
    }
	
    //	提供一个静态方法,返回实例对象
    public static Singleton getInstance() {
        return SingletonHoler.singleton;
    }

    //定义静态内部类
    private static class SingletonHoler {
        //当内部类第一次访问时,创建对象实例
        private static final Singleton singleton = new Singleton();
    }

}

/*
	这种方式采用了类装载的机制来保证初始化实例时只有一个线程
	静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,
	调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
	类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行	 初始化时,别的线程是无法进入的。
	避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
	推荐使用
*/

5) 枚举

package com.xin.singleton;

/*
    单例模式:枚举方式
 */
public enum Singleton {
    INSTANCE; //属性
}


//枚举测试
public class Test {
    public static void main(String[] args) {
        System.out.println("使用枚举单例");
        Singleton singleton1 = Singleton.INSTANCE;
        Singleton singleton2 = Singleton.INSTANCE;
        System.out.println(singleton1);  //INSTANCE
        System.out.println(singleton2);  //INSTANCE
        System.out.println(singleton1.hashCode());  //356573597
        System.out.println(singleton2.hashCode());  //356573597
    }
}

/*
	枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
	推荐使用
*/

三、单例模式注意事项

  • 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需 要频繁创建销毁的对象,使用单例模式可以提高系统性能

  • 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new

  • 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或 耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数 据库或文件的对象(比如数据源、session工厂等)

单例模式

原文:https://www.cnblogs.com/allure-xiaoxin/p/14643514.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!