博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
单例设计模式(这一篇足够了)
阅读量:7080 次
发布时间:2019-06-28

本文共 3329 字,大约阅读时间需要 11 分钟。

 

  单例模式真是一个老掉牙的问题了,不过我今天是要说些里面更深点的知识,闲话少说,直接来代码

  1、饿汉式

   相信这种写法大家都知道,一开始接触单例的时候,大家应该都是用的这种方法:

package com.hd.single;public class Singleton {    private Singleton(){}    private static Singleton instance = new Singleton();    public static Singleton getInstance(){        return instance;    }}

  这种方式优点就是线程安全, 缺点也很明显,就是类加载的时候,就已实例化该对象了,后面有可能用不到这个实例对象,这样就会造成空间浪费。因此就有了懒加载方式。

  2、懒汉式

  1)懒汉式L1

package com.hd.single;public class Singleton2 {    private Singleton2(){}    private static Singleton2 instance;    public static Singleton2 getInstance(){        if(instance == null)        //1            instance = new Singleton2();  //2        return instance;    }}

  这种懒汉式的优点和缺点也很明显,优点是按需加载,节省空间, 缺点是线程不安全。简单说就是,有可能线程A执行到“1”处时,阻塞住了,线程B抢到CPU,进来执行并实例化对象,然后线程A醒来后,继续往下执行,这样线程A和B取到的就是不同的对象。因此,又有了线程安全的版本。

package com.hd.single;public class Singleton2 {    private Singleton2(){}    private static Singleton2 instance;    public static synchronized Singleton2 getInstance(){        if(instance == null)            instance = new Singleton2();        return instance;    }}

  但是加了synchronized 之后会造成线程阻塞,影响性能。于是又提出了双检锁的方式

1 package com.hd.single; 2  3 public class Singleton2 { 4  5     private Singleton2(){} 6     private static Singleton2 instance; 7  8     public static Singleton2 getInstance(){ 9         if(instance == null){10             synchronized (Singleton2.class){11                 if(instance == null){12                     instance = new Singleton2();13                 }14             }15         }16         return instance;17     }18 }

  看似双检锁的方式很完美,既解决了线程安全的问题,又兼顾了性能问题: 线程先判断instance变量是否为空,如果不为空,则直接返回。否则进入同步块去实例化对象。但事实这是一个错误的优化!

  重点就是第12行代码(instance = new Singleton2();), 它创建了一个对象。这一行代码可以分解为如下的3行代码:

memory = allocate();         //1:分配对象的内存空间ctorInstance(memory);        //2:初始化对象instance = memory;           //3:设置instance指向刚分配的内存地址

  上面2和3这两步在执行的时候,有可能会被重排序(具体指令重排序知识点,可以去网上搜索相关内容,一大堆,我就不详细说了。本质就是jvm为了优化而使用的),2和3重排序之后的执行时序如下:

memory = allocate();         //1:分配对象的内存空间instance = memory;           //3:设置instance指向刚分配的内存地址                           //注意此时对象还没有被初始化ctorInstance(memory);        //2:初始化对象

  因此如果有线程A执行到3时,此时instance变量确实不为空,然后线程B判断instance不为空后返回,那么这是时程B 取到的就是一个空的对象。显示这样是有问题的,因此为了防止出现这个问题,我们可以使用volatile变量,来禁止指令重排序。

  2)懒汉式 L2(基于volatile的解决方案)

package com.hd.single;public class Singleton {    private Singleton(){}    private volatile static Singleton instance;    public static Singleton getInstance(){        if(instance == null){            synchronized (Singleton.class){                if(instance == null){                    instance = new Singleton3();                }            }        }        return instance;    }}

  我们除了通过volatile的方式来禁止指令重排序,还可以提供另外一种思路:允许2和3重排序,但不允许其它线程“看到”这个重排序。 前面正是因为线程B看到了重排序,发现instance变量不为空,所以才造成其取到空的对象。

  3)懒汉式L3(基于静态内部类的方案)

package com.hd.single;public class LazySingleton2 {    private LazySingleton2() {    }    static class SingletonHolder {        private static final LazySingleton2 instance = new LazySingleton2();    }    public static LazySingleton2 getInstance() {        return SingletonHolder.instance;    }}

  因为 在加载外部类时,其内部类不会同时被加载。只有调用 getInstance方法的时候,内部类才会去被加载,且只加载一次,不存在并发问题,因此是线程安全的。

  另外,在getInstance()方法中没有使用synchronized关键字,因此没有造成多余的性能损耗。

 

  本文给出了多个版本的单例模式,供我们在项目中使用。一般用L2,L3就基本够用。

 

转载于:https://www.cnblogs.com/xiexin2015/p/9043429.html

你可能感兴趣的文章
如何清除Xcode8打印的系统日志
查看>>
RAID概述
查看>>
mysql主从复制
查看>>
PHPWAMP站点管理的“域名模式”和“端口模式”详解、均支持自定义
查看>>
date(时间),timedatectl(时区),cal(日历)的用法
查看>>
maven pom.xml 配置初探
查看>>
Oracle GoldenGate Monitor架构图
查看>>
Elastic Stack学习--elasticsearch部署常见问题
查看>>
Oracle 手工清理临时段
查看>>
通过git远程管理自己本地的工程
查看>>
Python 数学操作符
查看>>
scala中的下划线_
查看>>
QTreeWidget 获取被双击的子项的层次路径
查看>>
如何调整工作激情
查看>>
数据仓库专题(10)-文本事实和杂项维度
查看>>
VC6下实现remove_reference的方法。
查看>>
数据备份和还原
查看>>
Angular企业级开发(3)-Angular MVC实现
查看>>
SMS系列之一:部署SMS2003 + SP3
查看>>
查看mysql进程--show processlist
查看>>