Java中的单例模式(Singleton Pattern in Java)

Posted quixotey

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中的单例模式(Singleton Pattern in Java)相关的知识,希望对你有一定的参考价值。

Introduction

对于系统中的某个类来说,只有一个实例是很重要的,比如只有一个timer和ID Producer。又比如在服务器程序中,配置信息保留在一个文件中,这些配置信息由一个单例对象统一获取,进程中的其他对象通过这个单例对象获取这些配置信息,这种方式能大大简化复杂环境下的配置管理。

所以这个时候一个类里面就只能有一个实例,而且这个实例要易于访问。我当然可以只定义一个全局变量可以保证对象随时都能访问,但是这种方式我依然可以实例化多个instance,而且被不同的对象所持有,不是很妙。

实现单例模式的思路是:

一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

如果类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。

Definition

Singleton Pattern:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

  1. 单例类只能由一个实例
  2. 自行创建实例
  3. 自行向整个系统(所有其他对象)提供此实例

Attention

在多线程的应用场合中使用单例模式的时候要注意。如果唯一的单例尚未被创建(懒汉模式),有两个线程同时调用构建的方法,他俩都不会检测到唯一实例,于是每个线程都创建了一个实例,这就违反了单例模式中实例唯一的原则。

所以我们可以为指示类是否实例化的变量提供一个互斥锁。(会降低效率)

Java实现

常用的构建方式

  • 懒汉模式。指全局的单例模式在第一次使用时被创建。
  • 饿汉方式。指全局的单例模式在类装载的时候被创建。

Example

  1. 饿汉模式
public class Sington
     private static Singleton instance = new Singleton();  
     private Singleton ()
     
     public static Singleton getInstance()   
     return instance;  
       

在类加载的时候完成初始化,所以类的加载速度会比较慢,但是可以很快的获取对象,优势在于不需要去考虑多线程的同步问题。

  1. 懒汉模式

    2.1 lock

    public class Singleton 
        private static Singleton instance;
    
        private Singleton() 
        
    
        public synchronized Singleton getInstance() 
            if (null == instance) 
                instance = new Singleton();
            
            return instance;
        
    

    这样来加锁是一个比较简单粗暴的方式,但是比较无脑synchronized,每次调用getInstance都会进行一次同步,会有一定的性能消耗,实际上我只需要第一次初始化的时候加锁就好了。

    2.2 double checked locking

public class Singleton 
    private static volatile Singleton instance = null;
  
    // Private constructor suppresses 
    // default public constructor
    private Singleton() ;
  
    //Thread safe and performance  promote 
    public static  Singleton getInstance() 
        if(instance == null)
             synchronized(Singleton.class)
                 // When more than two threads run into the first null check same time, 
                 // to avoid instanced more than one time, it needs to be checked again.
                 if(instance == null) 
                     instance = new Singleton();
                  
               
        
        return instance;
    
  

双重检查锁的优势在于它先判断了对象是否已经初始化,再决定要不要加锁。

使用了volatile关键字之后,所有的写操作发生在读操作之前。这个解决方案需要 JDK5 或更高版本(因为从 JDK5 开始使用新的 JSR-133 内存模型规范,这个规范增强了 volatile 的语义)

  1. Initialization On Demand Holder idiom

JVM 在类的初始化阶段(即在 Class 被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM 会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。

public class Singleton  
    public class InstanceFactory 
    private static class InstanceHolder 
        public static Instance instance = new Instance();
    

    public static Instance getInstance() 
        return InstanceHolder.instance ;  // 这里将导致 InstanceHolder 类被初始化 (只有第一次调用getInstance方法的时候,虚拟机加载InstanceHolder并且初始化instance)
    

 

不是很清楚这种基于类初始化的方案和上面的双重检查模式到底谁的性能更好。

但基于 volatile 的双重检查锁定的方案有一个额外的优势:除了可以对静态字段实现延迟初始化外,还可以对实例字段实现延迟初始化。

参考


单例模式

双重检查锁定与延迟初始化

以上是关于Java中的单例模式(Singleton Pattern in Java)的主要内容,如果未能解决你的问题,请参考以下文章

Java中的单例模式

怎么实现一个线程安全的单例模式

Java中的单例模式和静态类有啥区别? [复制]

Java的单例模式实现

如何对枚举类型实现的单例模式进行mock

Spring MVC系列Java中的单例模式