文档章节

设计模式学习---单例模式

 爸爸受不了
发布于 02/18 15:41
字数 1764
阅读 3.6K
收藏 5

单例模式---对于整个系统只需要一个实体就能完成工作的情况下,我们系统只需要一个实体并且保证只有一个实例,避免造成资源浪费

1.懒汉

懒汉模式是在需要用到该实例的时候才进行实例化    

优点:节约资源,在需要用到该实例的时候才初始化

缺点:线程非安全,并发访问情况下,有可能多次实例化,并且每次实例化都覆盖上一次的实例

public class Singleton {

    private static Singleton SINGLETON;

    private Singleton(){}

    public static Singleton getInstance(){
        if(Singleton.SINGLETON == null);
            Singleton.SINGLETON = new Singleton();
        return SINGLETON;
    }

}

2.饿汉

饿汉单例模式在类加载的时候就实例化

优点:安全,不存在并发创建多实例问题

缺点:容易造成资源浪费,一直占用着资源且无法回收

public class Singleton {

    private static final Singleton SINGLETON = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return SINGLETON;
    }

}

3.懒汉模式(方法加锁)

这种模式在获取实例的时候添加synchronize同步锁能避免多并发情况下造成创建多实例问题

优点:具有懒汉模式的节约资源优点,且方法加锁情况下避免了多并发创建多次实例的情况

确定:方法锁消耗性能比较大,必须是第一访问完整个方法才到第二次访问进入

public class Singleton {

    private static Singleton SINGLETON;

    private Singleton(){}

    public synchronized static Singleton getInstance(){
        if(Singleton.SINGLETON == null);
            Singleton.SINGLETON = new Singleton();
        return SINGLETON;
    }
}

4.双重锁校验(推荐)

双重锁校验是优化了方发锁的方式而来,优化啊了多并发情况下性能低下的结果

优点:保证了线程安全情况下,节约资源且访问性能高

public class Singleton {

    private static Singleton SINGLETON;

    private Singleton(){}

    public static Singleton getInstance(){
        if(Singleton.SINGLETON == null){
            synchronized (Singleton.class) {
                if(Singleton.SINGLETON == null) {
                    Singleton.SINGLETON = new Singleton();
                }
            }
        }
        return SINGLETON;
    }
}

进入方法体之后首先判断了实例是否存在,如果存在,则直接返回实例,否则加锁执行多一次判断,如果为null再实例化。因为第一次判断和加锁之间,对象可能已经实例化,所以加锁之后再判断一次,避免多次创建。但是这种方式还有点缺陷,synchronized关键字可以保证多线程情况下同步问题,如果是多核计算机(现在绝大部分都是多核计算机)情况下,还会有一个指令重排的问题所以我们需要用volatile 来修饰SINGLETON,最后改造成下面代码

public class Singleton {

    private volatile static Singleton SINGLETON;

    private Singleton(){}

    public static Singleton getInstance(){
        if(Singleton.SINGLETON == null){
            synchronized (Singleton.class) {
                if(Singleton.SINGLETON == null) {
                    Singleton.SINGLETON = new Singleton();
                }
            }
        }
        return SINGLETON;
    }
}

5.静态内部类

静态内部类是在调用的时候才会进行加载,是懒汉模式另外一种实现方式

public class Singleton {

    private Singleton(){}

    public static Singleton getInstance(){
        return Instance.singleton;
    }

    private static class Instance{
        private static final Singleton singleton = new Singleton();
    }
}

6.枚举

枚举为最优的单例模式实现方案,因为可以防反射暴力创建对象,也可以避免序列化问题,下面先放了一个简单的例子,

public enum SingletonEnum {

    SINGLETON;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static SingletonEnum getInstance(){
        return SINGLETON;
    }

}

看一下使用方式

public static void main(String[] args) {
    SingletonEnum.SINGLETON.setName("name1");
    System.out.println(SingletonEnum.SINGLETON.getName());
}

输出结果,由此可见 SingletonEnum.SINGLETON 时调用的都是同一个实例

下面我们看看枚举类型防放射暴力创建实例

我们用之前静态内部类的那个代码来比较

public static void main(String[] args) throws NoSuchMethodException,
        IllegalAccessException, InvocationTargetException, InstantiationException {
    // 反射获取构造器
    Constructor<Singleton> singletonConstructor = Singleton.class.getDeclaredConstructor();
    // 通过构造器创建对象
    Singleton singleton1 = singletonConstructor.newInstance();
    // 通过我们单例获取实例的接口获取实例
    Singleton singleton2 = Singleton.getInstance();
    // 下面结果为false,证明是2个不一样的实例,甚至都不用调用构造器的 setAccessible() 就能成功新建一个实例
    System.out.println(singleton1 == singleton2);
}

接下来再看看枚举

public static void main(String[] args) throws NoSuchMethodException,
        IllegalAccessException, InvocationTargetException, InstantiationException {
    // 反射获取构造器
    Constructor<SingletonEnum> singletonEnumConstructor = SingletonEnum.class.getDeclaredConstructor();
    // 通过构造器创建对象
    SingletonEnum singletonEnum1 = singletonEnumConstructor.newInstance();
    // 获取单例
    SingletonEnum singletonEnum2 = SingletonEnum.SINGLETON;
    // 下面结果为false,证明是2个不一样的实例,甚至都不用调用构造器的 setAccessible() 就能成功新建一个实例
    System.out.println(singletonEnum1 == singletonEnum2);
}

这时候报是报了个java.lang.NoSuchMethodException,原因是因为枚举类型没有无参构造

下面我们进入debug模式可以看到只有一个带一个String参数和一个int参数的构造方法

所以改造成

public static void main(String[] args) throws NoSuchMethodException,
        IllegalAccessException, InvocationTargetException, InstantiationException {
    // 反射获取构造器
    Constructor<SingletonEnum> singletonEnumConstructor = SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
    // 通过构造器创建对象
    SingletonEnum singletonEnum1 = singletonEnumConstructor.newInstance("",1);
    // 获取单例
    SingletonEnum singletonEnum2 = SingletonEnum.SINGLETON;
    // 下面结果为false,证明是2个不一样的实例,甚至都不用调用构造器的 setAccessible() 就能成功新建一个实例
    System.out.println(singletonEnum1 == singletonEnum2);
}

但是改过来之后报了个 java.lang.IllegalAccessException 非法访问异常

原因是如果实例化的对象是个枚举类型,就会抛出这个异常,这说明枚举类型天生就是单例的

public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");

序列化与反序列化,如果我们实体需要储存到程序以外的存储媒介,当再次获取时候,这个实例并非我们最开始的实例

序列化的时候实体类必须实现 Serializable

public class Singleton implements Serializable{}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    // 通过获取实例接口获取实例
    Singleton singleton1 = Singleton.getInstance();
    // 创建输出流并且输出到文件
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\singleton\\singleton.txt"));
    oos.writeObject(singleton1);

    // 创建输入流并且反序列化实例
    ObjectInputStream ios = new ObjectInputStream(new FileInputStream("D:\\singleton\\singleton.txt"));
    Singleton singleton2 = (Singleton) ios.readObject();
    oos.close();
    ios.close();
    System.out.println(singleton1 == singleton2);
}

序列化前后的对象结果对比,不是同一个实例

再看看枚举类型

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    SingletonEnum singletonEnum1 = SingletonEnum.SINGLETON;
    // 创建输出流并且输出到文件
    ObjectOutputStream oosE = new ObjectOutputStream(new FileOutputStream("D:\\singleton\\singletonE.txt"));
    oosE.writeObject(singletonEnum1);

    // 创建输入流并且反序列化实例
    ObjectInputStream iosE = new ObjectInputStream(new FileInputStream("D:\\singleton\\singletonE.txt"));
    SingletonEnum singletonEnum2 = (SingletonEnum) iosE.readObject();
    oosE.close();
    iosE.close();
    System.out.println(singletonEnum1 == singletonEnum2);
}

序列化前后的对象是一致的,没有被破坏

所以单例的最优方案是枚举,其他方法都会因为反射或者序列化破坏了整个系统只有一个实例的原则,当然根据业务要求选择一种比较合适目前开发团队的方案也很重要

© 著作权归作者所有

上一篇: vue-print打印
粉丝 8
博文 16
码字总数 29224
作品 0
广州
私信 提问
加载中

评论(0)

《PHP设计模式大全》系列分享专栏

《PHP设计模式大全》已整理成PDF文档,点击可直接下载至本地查阅 https://www.webfalse.com/read/201739.html 文章 php设计模式介绍之编程惯用法第1/3页 php设计模式介绍之值对象模式第1/5页...

kaixin_code
2018/11/06
223
0
你所学习的设计模式到底有什么用?到底怎么用?

前言 经过大约两个月的磨蹭,我终于是把设计模式打下来了。 具体详细在:http://www.cnblogs.com/linkstar/category/1087887.html 那么当我学习完成之后,所想到的第一件事,也就是你们标题上...

LinkinStar
2017/11/19
0
0
从工厂模式说起,简单聊聊设计模式在前端中的应用

设计模式是一套可以被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式,是为了可重用代码,让代码更容易被他人理解并且提高代码的可靠性。 设计模式的基本原则...

罗辑思维前端开发团队
2019/07/26
0
0
设计模式知识汇总(附github分享)

写在前面 主要内容 为了更系统的学习设计模式,特地开了这样一个基于Java的设计模式【集中营】,都是笔者在实际工作中用到过或者学习过的一些设计模式的一些提炼或者总检。慢慢地初见规模,也...

landy8530
2018/10/10
0
0
Rust语言 模式设计 持续更新中

学习了一下 Rust 语言,不像人们说的学习曲线很高,为了练手,用 Rust 把常用的设计模式实现了一遍,就当。 github 地址: https://github.com/lpxxn/rust-design-pattern 目前实现的有,会持...

li-peng
03/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

超实用企业级搜索引擎_Elasticsearch(二)基于RESTFul Api操作

Elasticsearch(二)基于RESTFul Api操作 想要进行API操作,必须安装好Elasticsearch,如果没安装的,可以参考上篇去操作一波,再来学习API操作噢! Elasticsearch的 API,我们可以不用每个API语法啥...

煌sir
13分钟前
15
0
版本控制git的简单使用

0.第一次使用时配置: git config --global user.name "your_name" git config --global user.email "your_name@domain.com" 用的最多的: (查看当前git状态) git status 1.初始化: ......

baowang123
28分钟前
5
0
定时器Timer和TimerTask

为什么要使用定时器呢? 比如说一个web应用,如果这个应用规模很大,那它的日志数据是不是很多。如果一直存下来服务器的存储量怕是不行吧,需要隔一段时间删除,那么就需要一个线程每隔一段时...

南柯好萌
45分钟前
18
0
深圳创服机构创成汇投融资对接指南

深圳创服机构创成汇投融资对接指南 一线城市一直是许多创业者创业热土,深圳也不例外,作为发达城市,科技是深圳的标志,也是许多科技创业者向往之地,科技创业者在创业前期面临许多难题,其...

cchlin
58分钟前
35
0
egg学习笔记第六天:使用中间件屏蔽可疑用户

站点有时候想屏蔽一些特定频繁抓取服务器数据的用户,可以放在中间件中去做,用户在指定Ip数组内,则屏蔽,如果不在,则匹配路由规则执行controller。 中间件的概念: 匹配路由前,匹配路由完...

一生懸命吧
今天
34
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部