单例模式浅析

  单例模式

  单例模式是一种比较常见的模式,看起来很简单,但是要做到高效安全的使用,其中还是有很多要点的。参考了Head First及众多网友的文章,稍微总结一下,以备查看。

  单例模式的定义:确保一个类只有一个实例,并且提供一个全局访问点。

1. 最简单的单例(饿汉模式),程序一加载就对 instance 变量进行初始化,好处是简单明了,缺点:如果初始化的耗费的资源特别多,而之后这个类可能未被使用到,就会造成浪费。

public class Singleton{
    // 单例类静态引用
    private static Singleton instance = new Singleton();
    // 私有构造方法
    private Singleton(){}
    // 静态方法:全局访问点
    public static Singleton getInstance(){
        return instance;
    }
}  

2. 延迟加载(懒汉模式),作为改进,我们可以在使用到的时候才去创建单例类。

public class LazySingleton {

    private static LazySingleton instance = null;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        // 使用的时候去检测是否已经初始化
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

  这种方式实现的单例是线程非安全的,假设有线程 A 和 B,同时去获取 LazySingleton 实例,当 A 线程进入 getInstance(),执行到 if (instance == null) 时,此时判断结果为真;之后切换到线程 B 执行,此时也执行到 if (instance == null) ,此时判断结果为真,会对 instance 变量进行初始化;此时切换回线程 A ,根据之前的判断结果,也会对 instance 变量进行初始化,此时就会得到两个不一样的实例,单例失败。

  为了实现线程安全,只要在 getInstance() 方法上添加 synchronized 关键字即可。

3. 双重校验锁

  加上 synchronized 关键字,实现了线程安全,但又带来了性能问题。通过同步方法会让性能大幅下降。

  通过代码我们可以看到,instance 变量在整个程序执行期间只会初始化一次,如果只为了这一次初始化,每次获取单例对象都必须在synchronized这里进行排队,实在太损耗性能。为此我们可以如下改进:① instance 变量只会初始化一次,把 synchronized 关键字加载此处;② 为了避免每次获取单例对象都在同步代码上等待,可以在同步代码块外层再加一次 instance == null 判断。

public class DoubleCheckSingleton {

    private static DoubleCheckSingleton instance = null;

    private DoubleCheckSingleton() {}

    public static DoubleCheckSingleton getInstance() {
        // 两层校验才能确保单例
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

4. volatile 关键字

  Java中的指令重排优化是指在不改变原语义(即保证运行结果不变)的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。这种优化在单线程中是没有任何问题的,但是在多线程中就会出现问题。

  创建一个变量需要2个步骤:一个是申请一块内存,调用构造方法进行初始化操作;另一个是分配一个指针指向这块内存。这两个操作谁在前谁在后呢?JVM规范并没有规定。那么就存在这么一种情况,JVM是先开辟出一块内存,然后把指针指向这块内存,最后调用构造方法进行初始化。假设线程A开始创建 SingletonClass 的实例,此时线程 B 调用了 getInstance() 方法,首先判断 instance 是否为 null。按照我们上面所说的内存模型,A 已经把 instance 指向了那块内存,只是还没有调用构造方法,因此 B 检测到 instance 不为 null,于是直接把 instance 返回了——问题出现了,尽管instance不为null,但它并没有构造完成,就像一套房子已经给了你钥匙,但你并不能住进去,因为里面还没有收拾。此时,如果B在A将instance构造完成之前就是用了这个实例,程序就会出现错误了!

  在 JDK1.5 及之后版本赋予了volatile关键字明确用法。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。所以只需要在 instance 变量前加上 volatile 修饰符即可避免Java指令优化带来的问题

public class DoubleCheckSingleton {

    private static volatile DoubleCheckSingleton instance = null;

    private DoubleCheckSingleton() {}

    public static DoubleCheckSingleton getInstance() {
        // 两层校验才能确保单例
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

注:volatile 关键字用法

  ① 可见性。可见性指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中(工作内存是线程独享的,主存是线程共享的)

  ② 禁止指令重排序优化。

5. 静态内部类

  上述 volatile 关键字收到 JDK 版本限制,如下的静态内部类实现方式是确保了延迟加载和线程安全的。

public class StaticInnerSingleton {

    private static class Holder {
        private static StaticInnerSingleton instance = new StaticInnerSingleton();
    }

    private StaticInnerSingleton() {
    }

    public static StaticInnerSingleton getSingleton() {
        return Holder.instance;
    }
}

  可以把 StaticInnerSingleton 实例放到一个静态内部类中,这样就避免了静态实例在 StaticInnerSingleton 类加载的时候就创建对象,并且由于静态内部类只会被加载一次,而类的构造必须是原子性的,非并发的,所以这种写法也是线程安全的

  在上述代码中,因为 StaticInnerSingleton 没有 static 修饰的属性,因此并不会被初始化。直到调用 getInstance() 的时候,会首先加载 Holder

类,这个类有一个 static 的 StaticInnerSingleton 实例,因此需要调用 StaticInnerSingleton 的构造方法,然后 getInstance() 将把这个内部类的instance 返回给使用者。由于这个 instance 是 static 的,因此并不会构造多次。

6. 枚举式单例

public enum EnumSingleton {

    INSTANCE;

    private String name;

    public String getName() {
        return name;
    }

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

    public String getSomething() {
        return "String";
    }
}

  使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》书中推荐使用的方法。

参考: 深入Java单例模式  http://devbean.blog.51cto.com/448512/203501/

    你真的会写单例模式吗--Java实现 http://www.importnew.com/18872.html 

    感谢二位作者文章,本文仅作整理学习,侵删!   

时间: 2024-08-05 12:09:23

单例模式浅析的相关文章

PHP单例模式浅析

首先我们要明确单例模式这个概念,那么什么是单例模式呢?单例模式顾名思义,就是只有一个实例.作为对象的创建模式, 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类我们称之为单例类. 单例模式的要点有三个:   它们必须拥有一个构造函数,并且必须被标记为private   它们拥有一个保存类的实例的静态成员变量   它们拥有一个访问这个实例的公共的静态方法 和普通类不同的是,单例类不能在其他类中直接实例化.单例类只能被其自身实例化.要获得这样的一种结果, __cons

浅析单例模式

单例模式是设计模式中最简单的形式之一.这一模式的目的是使得类的一个对象成为系统中的唯一实例. 单例代码如下: private static JobManager _instance;        private static Object padlock = new Object();        public static JobManager Instance        {            get            {                if (_instanc

浅析单例模式与线程安全(Linux环境c++版本)

什么是单例模式 单例模式是设计模式中一种常用模式,定义是Ensure a class has only one instance, and provide a global point of access to it.(确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例) 用<设计模式之禅>里的话说,就是,在一个系统中,要求一个类有且仅有一个对象,如果出现多个就会出现"不良反应",比如以下情景可能会用到单例模式 要求生成唯一序列号的环境 在整个项目中需要一个共享

浅析java单例模式

单例模式:运行期间有且仅有一个实例 一. 关键点: 1.一个类只有一个实例------最基本的-----(只提供私有构造器) 2.该类必须自行创建这个实例-----(定义了静态的该类的私有对象) 3.该类必须自行向整个系统提供这个实例---(提供一个静态的公有方法,返回创建或者获取本身的静    态私有对象) 二.基本单例模式 1.懒汉模式 懒汉模式:在类加载的时候,不创建实例,运行调用的时候创建(以时间换空间) 优缺点:类加载快,在运行时获取速度慢:线程不安全: 解决懒汉模式线程安全方法:(1

C#浅析单例模式

第一次写博客,写的不好休怪哈. 版本1:最简单的单例模式 方法一: public class MySingleton { private MySingleton() //构造函数,注意private { } private static MySingleton _Entity = null; //私有字段 public static MySingleton Entity //公共属性 { get { if (_Entity == null) //确保只能创建一次对象 { MySingleton.

浅析JAVA设计模式之单例模式(一)

1 单例模式简介 单例模式确保一个类只有一个实例,并且自行实行实例化并向整个系统提供这个实例. 单例模式有三个要点: 1.某个类只能有一个实例. 2.它必须自行创建这个实例. 3.它必须向整个系统提供这个实例. 单例模式主要分为两种:饿汉式单例模式和懒汉式单例模式 1.1饿汉式单例模式: 饿汉式单例模式是java语言实现起来最为简单的单例模式,UML图如下: 图1.1 从图中可以看出,它自己将自己实例化. 1.2饿汉式单例模式的实现(建一个Singleton包,所有程序放在该包下): (1)建一

单例模式与线程安全问题浅析

近期看到到Struts1与Struts2的比較.说Struts1的控制器是单例的,线程不安全的:Struts2的多例的,不存在线程不安全的问题.之后又想到了之前自己用过的HttpHandler... 这些类.好像单例的线程安全问题确实是随处可见的. 可是仅仅是知道这个是不安全的,也没有认真分析过.接下来就细致分析下. 一,改动单例模式代码 首先我先写一段单例类的代码: /** * @ClassName: Sigleton * @Description: 单例类 * @author 水田 * @d

浅析Java设计模式 - 单例模式

以下是三种单例模式的代码实现,前两者用的比较多 (言外之意 最后一种可以忽略) 1 package com.signle; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 /** 7 * 8 * @title 单例模式 9 * @Copyright Copyright (c)2016年3月9日 10 * @Company CTC 11 * @version 1.0 12 * @author ejokovic 13 * @time

浅析Javascript单例模式

定义 保证一个类仅有一个实例,并提供一个访问它的全局访问点 .就想我们在开发中有些对象只需要一个,例如window对象. 1. 实现单例模式 var Singleton = function( name ) { this.name = name; }; Singleton.prototype.getName = function() { alert ( this.name ); }; Singleton.getInstance = function( name ) { if ( !this.in