博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[工作中的设计模式]单例模式singleton
阅读量:5031 次
发布时间:2019-06-12

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

一、模式解析:

单例模式是最简单和最常用的设计模式,面试的时候,不管新毕业的学生还是已经工作多年的筒子,对单例模式基本都能聊上两句。单例模式主要体现在如下方面:

  1、类的构造函数私有化,保证外部不能直接使用构造函数创建类的实例

  2、提供获取实例的方法,外部可以通过此方法获取已经创建好的实例对象,

  3、获取实例的方法必须保证实例唯一性。

  4、根据获取实例方法保证唯一性的方式,单例模式又分为以下几种:

二、模式代码

1、懒汉模式

/** * 单例模式-懒汉模式 * 懒汉模式意味着此模式比较懒惰,直到系统发起调用时候才会创建此对象的实例。 * 需要注意的是要保证线程的同步,防止出现多个实例创建的情况 * @author zjl * */public class Singleton1 {    //创建元素实例    private static Singleton1 singleton;    //构造对象置为私有变量,不能从外部进行创建    private Singleton1(){            }    /**     * public方法,在外部调用时候,获取此对象的实例     * 注意需要使用同步方法,防止多线程时候产生多实例     * @return 单例对象实例     */    public static synchronized Singleton1 getInstance(){        if(singleton==null){            singleton=new Singleton1();        }        return singleton;    }}

懒汉模式实现了对象的懒加载,不过缺点是由于getInstance上加了同步关键字,导致此方法只能有一个线程访问,效率会比较低

2、为了解决懒汉模式的速度问题,引入检测,也就是加锁之前先做一次判定,并将实例声明为volatile

public class Singleton {    private volatile static Singleton instance; //声明成 volatile    private Singleton (){}    public static Singleton getSingleton() {        if (instance == null) {                                     synchronized (Singleton.class) {                if (instance == null) {                           instance = new Singleton();                }            }        }        return instance;    }   }

3、痴汉模式

/** * 单例模式的痴汉模式 * 痴汉模式表示此模式很急,一旦对象初始化,立马创建一个实例,以后获得实例的都采用此实例 * @author zjl * */public class Singleton2 {    /**     * 私有构造函数,保证不能从外部创建实例     */    private Singleton2(){}    //痴汉模式重点,对象初始化直接创建实例    private static Singleton2 singleton=new Singleton2();        /**     * 直接返回之前创建的实例     * @return 对象实例     */    public static Singleton2 getInstance() {        return singleton;    }}

痴汉模式不会存在线程同步问题,但是缺点是不是懒加载,对象创建后立马创建实例。

4、静态内部类

/** * 使用静态内部类来创建单例 * @author zjl * */public class Singleton3 {    //私有化构造函数,使他不能在外部被创建    private Singleton3(){};    //创建静态内部类,初始化成员变量    private static class SingletonHodler{        private static final Singleton3 INSTANCE=new Singleton3();    }    /**     * 获取实例方法,执行时候才去创建实例     * @return     */    public static final Singleton3 getInstance(){        return SingletonHodler.INSTANCE;    }}

静态内部类创建的方法使用不多,也是在面试中很少了解到的,但却是最为推荐的方法,因为他实现了懒加载,线程安全且不需要依赖jdk版本。

三、应用场景

单例模式在框架中应用较多,比如spring的bean管理可以设置是否为单例模式,数据库对象实例化

四、场景代码

由于比较简单,略过。

五、疑问解决

为什么我们在做懒汉模式的双重检测的时候,需要将对象改变为使用volatile进行修饰,此处与java的内存模式有关,并涉及到synchronized和volatile的内存操作

1、java的内存模型主要分为主内存和工作内存,对应计算机的结构可以认为是计算机内存和cpu的高速缓存,对于任何主内存的数据进行操作时候,必须先将数据读取到工作内存形成一个备份,当对工作内存的数据操作完成后,将工作内存数据重新同步到主内存。

  对于工作内存和主内存的操作主要为:读取过程-read,load,use,写入过程 assign、store、write,这六个操作均具有原子性,但整个流程不是原子性。因此为了保证可见性和原子性,增加了lock和unlock操作,主要针对主内存区数据的锁定,一旦主内存区数据被lock,表示此线程独占了变量,不可被其他线程更改

内存模型

2、synchronized的作用主要有两点:程序临界区和内存同步。

  程序临界区:是一段仅允许一个线程进行访问的程序,一旦一个线程进入临界区,另外线程进入临界区只能在临界区外进行等待,等临界区线程执行完毕后,其他线程开始争夺资源,胜利者进入临界区,其他线程继续等待。

  内存同步:主要是主内存和工作内存的同步。进入临界区时,如果有工作内存的数据未被同步的主内存,则先进行同步,成为其他线程可见状态。工作内存的数据会被丢失,如果要使用,需要重新进行read和load操作。退出临界区时,将工作内存数据写入主内存,保证修改可见。

3、volatile的作用是保证数据可见性,对于任何工作内存的assign操作会立刻store和write到主内存中,同时使其他工作内存放弃原有持有数据。但是volatile不保证操作原子性。

4、具体分析可能出现的问题:

public class Singleton {    private static Singleton instance; //声明成 volatile    private Date date=new Date;    private Singleton (){}    public static Singleton getSingleton() {        //1        if (instance == null) {                     //2            synchronized (Singleton.class) {        //3                if (instance == null) {             //4                    instance = new Singleton();     //5                }            }                                       //6        }        return instance;                             //7    }    public Date getDate(){       return date;      }     }

  如上边的代码,我们将原有懒汉模式稍作修改,增加了date字段,模拟双线程A与B的创建过程

由于new Singleton()不是一个原子操作,程序在进入和出临界区时候,均会同步主内存的内容,除此之外B线程如果在3和6之间调用,就会发生A线程的工作线程内的内容不一定全部写入了主内存,假设此阶段instance写入了主内存,但是date没有写入,B线程将对data内容不可见,因此getDate将返回null。

如果对instance添加了volatile,那么针对instance的修改,随时b线程都是可见的。

转载于:https://www.cnblogs.com/jyyzzjl/p/5150110.html

你可能感兴趣的文章
Spring整合hibernate:3、使用XML进行声明式的事务管理
查看>>
SqlServer之Convert 函数应用格式化日期(转)
查看>>
软件测试领域中的10个生存和发展技巧
查看>>
Camera前后摄像头同时预览
查看>>
HDU 1856
查看>>
课堂作业01--架构师的职责
查看>>
iOS计算富文本(NSMutableAttributedString)高度
查看>>
2017/09/15 ( 框架2)
查看>>
Centos下源码安装git
查看>>
gulp-rev-append md5版本号
查看>>
IO流之File类
查看>>
sql 基础语句
查看>>
CF717A Festival Organization(第一类斯特林数,斐波那契数列)
查看>>
oracle直接读写ms sqlserver数据库(二)配置透明网关
查看>>
控件发布:div2dropdownlist(div模拟dropdownlist控件)
查看>>
Oracle composite index column ordering
查看>>
ActiveReports 报表控件官方中文入门教程 (3)-如何选择页面报表和区域报表
查看>>
kaggle竞赛
查看>>
区块链入门教程
查看>>
域 搭建OU 组织单元
查看>>