`
liugang594
  • 浏览: 989724 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java单例(Singleton)

 
阅读更多

【译自:http://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-with-examples

单例是设计模式中提到的模式之一,它属于创建模式的类别。从它的定义来看,这是一种非常简单的模式,但是当具体实现时,会发现它有很多需要留意的方面。关于单例的实现方法在开发人员中已经产生过很多讨论和争议。这里我们会学到关于单例的的一些准则,不同的实现方法和一些使用上的最佳实践。

单例模式

单例模式限制了类的实例化,确保只有一个类的实例存在于Java虚拟机中。这个单例类必须提供一个全局的访问点去得到这个类的唯一实例。单例通常用在例如日志、驱动、缓存和线程池等地方;另外它也可以用在其他的一些设计模式中,例如 Abstract Factory、 Builder、 PrototypeFacade 等。在核心Java类中,有很多单例的类,像 java.lang.Runtime,java.awt.Desktop等。

Java的单例模式

有很多实现单例模式的方法,但是他们都有一些共同的、需要关注的概念:

  • 私有的构造函数:限制其他类对它的实例化
  • 私有的静态变量:用于持有唯一的当前类的实例
  • 公开的静态方法 :用于返回当前类的唯一实现,这个方法也是提供给外部的全局访问点

接下来的部分,我们会学到几种不同的实现单例的方式和相应的需要关注的要求:

  1. 静态变量初始化
  2. 静态初始化块
  3. 延迟初始化
  4. 线程安全的单例
  5. Helper类实现
  6. 反射破坏初始化
  7. 枚举单例
  8. 系统化和单例

静态变量初始化

这种方式下,单例的实例在类被加载的时候就被初始化,这也是创建单例的最简单的方式。缺点就是即使这个实例没有被用到,也被创建了。

下面是这种实现方式的代码示例:

package com.journaldev.singleton;

public class EagerInitializedSingleton {
    
    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
    
    //private constructor to avoid client applications to use constructor
    private EagerInitializedSingleton(){}

    public static EagerInitializedSingleton getInstance(){
        return instance;
    }
}

如果你的单例没有占用多少资源,那么可以选择这种实现方式。然后大多数情况下,单例都是针对资源而创建的,例如文件系统,数据库连接等等,所以我们应该避免对它的实例化,除非调用端调用getInstance()。方法;另外这种实现方式没有提供任务异常处理逻辑。 

静态初始化块

静态初始化块的实现方式类似于上面的静态变量初始化,除了一点:静态初始化块可以提供异常处理:

package com.journaldev.singleton;

public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;
    
    private StaticBlockSingleton(){}
    
    //static block initialization for exception handling
    static{
        try{
            instance = new StaticBlockSingleton();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }
    
    public static StaticBlockSingleton getInstance(){
        return instance;
    }
}

静态变量和静态块都是在变量在使用之前就被创建了,因此不是一个好的最佳实践方式。在接下来的片段里,我们会学到如果延迟创建单例的实例。

延迟初始化

延迟初始化是通过使用一个全局访问的方法去创建单例的实例。例如下面的代码:

package com.journaldev.singleton;

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;
    
    private LazyInitializedSingleton(){}
    
    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

上面的代码在单线程的情况下工作的很好,不过在多线程环境中,如果有多个有线程同时访问,则可能存在问题,单例模式可能被破坏,不同的线程将得到不同的单例的实现。接下来我们看一种线程安全的实现方式。

线程安全的单例

最简单的创建线程安全的单例就是使得全局访问方法是同步的,这样一次只有一个线程可以执行这个方法。下面代码展示了这一点:

package com.journaldev.singleton;

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;
    
    private ThreadSafeSingleton(){}
    
    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }
    
}

上面的实现方式工作的很好,并且提供了线程安全的保障,但是它降低了性能,因为同步上的开销,同步应该只需要在一开始创建实例的时候用到。双重锁定可以用来避免每次访问的这些额外的开销,在这种实现方式下,同步块被包含在一个附加的检测中,以保证只有一个实例被创建,以下是代码实现:

    public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
        if(instance == null){
            synchronized (ThreadSafeSingleton.class) {
                if(instance == null){
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }

ReadThread Safe Singleton Class

BillPugh单例实现方式

在Java5之前,Java内存模型有很多问题,使得上面的双重锁定实现方式在某些情况下,例如很多的线程同时试图去访问单例实例,会失效。因此Bill Pugh提出了另一种不同的实现方式,通过使用内部静态助手类来实现。如下:

package com.journaldev.singleton;

public class BillPughSingleton {

    private BillPughSingleton(){}
    
    private static class SingletonHelper{
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
    
    public static BillPughSingleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}

注意:私有的、内部的、静态的类包含了单例的实例。当单例的类被加载时,这个内部的私有的类并没有被加载,只有当调用getInstance()方法的时候,这个类才会加载,然后创建单例的实例。这种实现方式被广泛的应用,并且不要求同步。我在很多工程都都使用这种方法,它很容易被理解。

ReadJava Nested Classes

反射破坏单例

反射能够破坏以上所有的实现方式:

package com.journaldev.singleton;


import java.lang.reflect.Constructor;


public class ReflectionSingletonTest {

    public static void main(String[] args) {
        EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton instanceTwo = null;
        try {
            Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                //Below code will destroy the singleton pattern
                constructor.setAccessible(true);
                instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(instanceOne.hashCode());
        System.out.println(instanceTwo.hashCode());
    }

}

当运行上面的测试类时,会发现两个实例的hashCode不一样,这证明单例被破坏了。反射很强大,并且用在很多的框架中,例如Spring, Hibernate等,请查看 Java Reflection Tutorial.

枚举单例

为了克服反射的问题,Joshua Bloch建议使用枚举去实现单例模式,因为Java确保枚举值只会被初始化一次,因为Java枚举是全局访问的,因此也是一个单例。不过缺点就是枚举不够灵活,并且不允许延迟加载。

package com.journaldev.singleton;

public enum EnumSingleton {

    INSTANCE;
    
    public static void doSomething(){
        //do something
    }
}

ReadJava Enum

系列化和单例

有时在分布式的系统中,需要在单例上实现系列化,这样就可以把它的状态存储在文件系统上,然后在稍后的某个点将它取回,例如:

package com.journaldev.singleton;

import java.io.Serializable;

public class SerializedSingleton implements Serializable{

    private static final long serialVersionUID = -7604766932017737115L;

    private SerializedSingleton(){}
    
    private static class SingletonHelper{
        private static final SerializedSingleton instance = new SerializedSingleton();
    }
    
    public static SerializedSingleton getInstance(){
        return SingletonHelper.instance;
    }
    
}

系列化一个单例的问题在于任何时候,当反系列化时,都会创建一个新的实例,例如:

package com.journaldev.singleton;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class SingletonSerializedTest {

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        SerializedSingleton instanceOne = SerializedSingleton.getInstance();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "filename.ser"));
        out.writeObject(instanceOne);
        out.close();
        
        //deserailize from file to object
        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "filename.ser"));
        SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
        in.close();
        
        System.out.println("instanceOne hashCode="+instanceOne.hashCode());
        System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
        
    }

}

以下是上面程序的输出:

instanceOne hashCode=2011117821
instanceTwo hashCode=109647522

因此它也破坏了单例模式,这种情况下,我们需要实现readResolve()方法以阻止实例的创建:

    protected Object readResolve() {
        return getInstance();
    }

然后就可以验证两个实例的hashCode相同。

分享到:
评论

相关推荐

    Java 单例模式Singleton

    简单的单例模式举例Singleton 分为恶汉式 懒汉式

    java单例模式实例

    在Java中,有多种实现单例模式的方法,每种都有其特点和适用场景。接下来,我们将深入探讨这些实现方式。 首先,我们来看**懒汉式(Lazy Initialization)**。这种实现方式是在类被首次请求时才创建单例对象,延迟...

    深入浅出单例Singleton模式

    【深入浅出单例Singleton模式】 单例模式是一种在软件设计中常见的设计模式,它的核心目标是确保一个类只有一个实例,并提供一个全局访问点。在Java等面向对象编程语言中,单例模式常用于控制资源的共享,如全局...

    Java 单例模式 工具类

    Java中的单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式常用于控制资源的访问,比如数据库连接池、线程池或者日志对象等。本篇文章将深入探讨如何在Java中...

    Java单例模式设计

    Java单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供全局访问点。这种模式在需要频繁创建和销毁对象的场景中,或者当对象昂贵时(如数据库连接),能够节省系统资源,提高效率。本篇文章将深入探讨...

    JAVA单例模式的几种实现方法

    ### JAVA单例模式的几种实现方法 #### 一、饿汉式单例类 饿汉式单例类是在类初始化时就已经完成了实例化的操作。这种实现方式简单且线程安全,因为实例化过程是在编译期间完成的,不会受到多线程的影响。 **代码...

    java单例模式的例子

    Java单例模式是一种常见的设计模式,它在软件工程中用于控制类的实例化过程,确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统资源管理、缓存、日志记录等方面应用广泛。下面我们将深入探讨Java单例...

    Java单例模式的全面总结

    Java中的单例模式主要分为三种实现方式:懒汉式单例、饿汉式单例和登记式单例。 1. 懒汉式单例(Lazy Initialization) 懒汉式单例的特点是在类被加载时不创建实例,而是在首次调用`getInstance()`方法时才进行实例...

    Java单例模式与工厂模式简单示例代码

    在Java中,单例模式的实现通常有几种方法: 1. **饿汉式(静态常量)**:在类加载时就完成初始化,所以类加载比较慢,但获取对象的速度快,且线程安全。 ```java public class Singleton { private static final ...

    Singleton(单例模式)

    在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样有几个好处: 1、某些类创建比较频繁,对于一些大型的对象,这可以节省一笔很大的系统开销。 2、省去了new操作符,降低了系统内存的使用频率...

    java单例模式代码实例

    Java单例模式是一种常用的设计模式,它用于保证一个类只有一个实例,并提供全局访问点。这种模式在系统资源有限或者需要共享昂贵对象时尤其有用,比如数据库连接池、线程池等。下面我们将深入探讨Java单例模式的实现...

    Java单例模式深入理解

    Java单例模式是一种设计模式,它允许在程序中创建唯一一个类实例,通常用于管理共享资源,例如数据库连接、线程池或者配置对象等。单例模式的核心在于限制类的构造函数,确保类只能被初始化一次,从而实现全局唯一的...

    java Singleton单例模式

    Java中的Singleton(单例模式)是一种常用的软件设计模式,它保证了类只有一个实例,并提供一个全局访问点。这种模式在需要频繁创建和销毁对象的场景中特别有用,因为它可以节省系统资源,例如数据库连接或者线程池...

    单例模式Singleton(java源码)

    单例模式的特点有三: 单例类只能有一个实例。 单例类必须自己创建自己的唯一实例。 单例类必须给所有其他对象提供这一实例。 Singleton模式包含的角色只有一个,就是Singleton。Singleton拥有一个私有构造函数,...

    快速理解java单例的小程序

    Java单例模式是一种设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这个模式在许多场景下非常有用,比如管理共享资源、配置对象或者创建昂贵的对象时。本小程序就是为了帮助开发者快速理解和掌握Java中...

    单例 singleton txt

    单例模式(Singleton Pattern)是一种常用的软件设计模式,属于创建型模式之一。其主要目的是确保一个类只有一个实例,并提供一个全局访问点来访问该实例。这在某些情况下非常有用,比如系统中需要频繁创建后又销毁...

    17-Java单例模式的学习笔记1

    Java 单例模式是一种设计模式,它用于保证一个类只有一个实例,并提供全局访问点。这种模式在需要控制类的实例化次数,或者当类的创建是昂贵的操作时非常有用。以下是对不同单例实现方式的详细说明: 1. **懒汉式**...

    java单例模式开发的7种写法

    ### Java单例模式开发的七种写法 #### 概述 单例模式是一种常用的软件设计模式,其目的是确保一个类仅有一个实例,并提供一个全局访问点。在Java编程语言中,实现单例模式的方法有很多种,不同的实现方式具有不同的...

    JAVA单例模式(三种)

    Java单例模式是一种常用的软件设计模式,它的核心思想是确保一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式被广泛应用于控制资源的共享,例如数据库连接池、线程池或者日志系统等。在本篇文章中,...

Global site tag (gtag.js) - Google Analytics