java设计模式-单例设计模式(一)

java设计模式-单例设计模式(一)

Time: 12/4/2017 9:17:18 PM
Author: Hongliang Zhu
Email:hongliangzhu1002@163.com

单例模式第一版:懒汉模式

public class Singleton {

    private Singleton() {}  //私有构造函数

    private static Singleton instance = null; //单例对象

   //静态工厂方法

    public static Singleton getInstance() {


        if (instance == null) {

        instance = new Singleton();

     }

    return instance;

    }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

关键要点:

  1. 要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此signleton的构造方法是私有的。

  2. instance是singleton类的静态成员,也是我们的单例对象。它的初始值可以写成null,也可以写成new singleton()。至于其中的区别后来会做解释。

  3. getinstance是获取单例对象的方法。

如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式。 
如果单例对象一开始就被new singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式。 
这两个名字很形象:饿汉主动找食物吃,懒汉躺在地上等着人喂。


但是以上的这段代码并非线程安全的,为什么说刚才的代码不是线程安全呢?:

假设Singleton类刚刚被初始化,instance对象还是空,这时候两个线程同时访问getInstance方法:

   
   
  • 1
  • 2

因为Instance是空,所以两个线程同时通过了条件判断,开始执行new操作:

   
   
  • 1
  • 2

这样一来,显然instance被构建了两次。让我们对代码做一下修改:

   
   
  • 1
  • 2

单例模式第二版:线程安全

 public class Singleton {

    private Singleton() {}  //私有构造函数

    private static Singleton instance = null;  //单例对象

     //静态工厂方法

    public static Singleton getInstance() {

    if (instance == null) {  //双重检测机制

        synchronized (Singleton.class){  //同步锁

        if (instance == null) { //双重检测机制

            instance = new Singleton();

            }

        }

     }

        return instance;

    }

}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

为什么这样写呢?我们来解释几个关键点:

  1. 为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。
  2. 进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。

像这样两次判空的机制叫做双重检测机制

但是,上面的单例模式第二版并非绝对的线程安全: 
假设这样的场景,当两个线程一先一后访问getInstance方法的时候,当A线程正在构建对象,B线程刚刚进入方法:

这种情况表面看似没什么问题,要么Instance还没被线程A构建,线程B执行 if(instance == null)的时候得到false;要么Instance已经被线程A构建完成,线程B执行 if(instance == null)的时候得到true。

真的如此吗?答案是否定的。这里涉及到了JVM编译器的指令重排。

指令重排是什么意思呢?比如java中简单的一句 instance = new Singleton,会被编译器编译成如下JVM指令:

memory =allocate(); //1:分配对象的内存空间

ctorInstance(memory); //2:初始化对象

instance =memory; //3:设置instance指向刚分配的内存地址

但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:

memory =allocate(); //1:分配对象的内存空间

instance =memory; //3:设置instance指向刚分配的内存地址

ctorInstance(memory); //2:初始化对象

当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程B抢占到CPU资源,执行 if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。如下图所示:


如何避免这一情况呢?我们需要在instance对象前面增加一个修饰符volatile。


单例模式第三版:修饰符volatile

public class Singleton {

    private Singleton() {}  //私有构造函数

    private volatile static Singleton instance = null;  //单例对象

    //静态工厂方法

    public static Singleton getInstance() {

        if (instance == null) {  //双重检测机制

            synchronized (this){  //同步锁

                if (instance == null) { //双重检测机制

                    instance = new Singleton();

                }

            }

        }

        return instance;

    }

}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

那么问题来了,volatile到底是个什么玩意儿呢?在维基百科中是这样说的:

The volatile keyword indicates that a value may change between different accesses, it prevents an optimizing compiler from optimizing away subsequent reads or writes and thus incorrectly reusing a stale value or omitting writes.

简单点说就是,volatile修饰符阻止了变量访问前后的指令重排,保证了指令的执行顺序。

经过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:

  1. memory =allocate(); //1:分配对象的内存空间

  2. ctorInstance(memory); //2:初始化对象

  3. instance =memory; //3:设置instance指向刚分配的内存地址

    如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。


说了那么多,其实这种方法还是有漏洞的,如果通过【反射】的方式,仍然可以构建多个对象。这个问题我会在单例设计模式(二)中解决,在哪里我会介绍一种更为优雅的单例设计模式实现。

说明:

  • volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。

第一次尝试使用MarkDown语法,还不太适应,得多加练习! 
12/4/2017 9:45:34 PM 工大软件园


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
1. 智慧监狱概述 智慧监狱的建设背景基于监狱信息化的发展历程,从最初的数字化监狱到信息化监狱,最终发展到智慧监狱。智慧监狱强调管理的精细化、监管的一体化、改造的科学化以及办公的无纸化。政策上,自2017年以来,司法部连续发布了多项指导性文件,推动智慧监狱的建设。 2. 内在需求与挑战 智慧监狱的内在需求包括数据应用与共享的不足、安防系统的单一功能、IT架构的复杂性、信息安全建设的薄弱以及IT运维的人工依赖。这些挑战要求监狱系统进行改革,以实现数据的深度利用和业务的智能化。 3. 技术架构与设计 智慧监狱的技术架构包括统一门户、信息安全、综合运维、安防集成平台和大数据平台。设计上,智慧监狱采用云计算、物联网、大数据和人工智能等技术,实现资源的动态分配、业务的快速部署和安全的主动防护。 4. 数据治理与应用 监狱数据应用现状面临数据分散和共享不足的问题。智慧监狱通过构建数据共享交换体系、数据治理工具及服务,以及基于数据仓库的数据分析模型,提升了数据的利用效率和决策支持能力。 5. 安全与运维 智慧监狱的信息安全建设涵盖了大数据应用、安全管理区、业务区等多个层面,确保了数据的安全和系统的稳定运行。同时,综合运维平台的建立,实现了IT系统的统一管理和自动化运维,提高了运维效率和系统的可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值