一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

java设计模式之单例模式(Singleton pattern)

时间:2016-08-01 编辑:简简单单 来源:一聚教程网

单例模式的定义:
Singleton pattern restricts the instantiation of a class and ensures that only one instance of the class exists in the java virtual machine. The singleton class must provide a global access point to get the instance of the class.

       限制一个类的实例在一个jvm实例中确保只有一个,而且必须提供一个全局访问点获得该单例。
为什么会出现单例模式:
•    减少内存开支。由于单例在内存中相对于一个jvm实例内只有一个实例对象,不会重复的创建和jvm垃圾回收,对于内存减少了空间占用,也利于jvm垃圾回收处理。
•  减少系统性能开销。当一个对象的产生依赖较多的资源时,比如读取配置或者依赖其他对象的时候,单例在jvm启动的时候预加载资源,然后可以永久驻留内存,当然也减少了jvm的垃圾回收线程的负担。
•当然还有很有的优势,现流行的spring框架就是默认支持单例模式(相对应spring容器)。
单例的实现方式:
     实现单例模式的方式有很多不同手段,但以下几点我们会同时考虑:
•构造函数必须私有,不能让别人有权限随意实例化
•该单例一般在类中有一个私有静态变量
•该单例一般提供一个静态公共方法获得该单例(对于外界的该单例的全局访问点)


 
饿汉式(Eager initialization)
   饿汉式单例实现方式是在类加载的时候初始化该单例。这种方式实现单例最简单,但也有个缺点就是即使我们应用中没有使用该类的单例,但类加载的时候也必须初始化。


package com.doctor.design_pattern.singleton;

/**
 * @author sdcuike
 *
 *         Created on 2016年7月31日 下午11:36:05
 *
 *         饿汉式 单例
 *         EagerInitialized Singleton
 */
public class EagerInitializedSingleton {
    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

    private EagerInitializedSingleton() {
    }

    public static EagerInitializedSingleton getInstance() {
        return instance;
    }

    public void doSomething() {
        System.out.println("test");
    }

    public static void main(String[] args) {
        System.out.println(EagerInitializedSingleton.getInstance());
        System.out.println(EagerInitializedSingleton.getInstance());
        EagerInitializedSingleton.getInstance().doSomething();
        // com.doctor.design_pattern.singleton.EagerInitializedSingleton@2a139a55
        // com.doctor.design_pattern.singleton.EagerInitializedSingleton@2a139a55
        // test

        System.out.println(EagerInitializedSingleton.getInstance() == EagerInitializedSingleton.getInstance());
        // true
    }

}

   当单例没有涉及到过多的资源使用的时候,饿汉式单例比较适合。但很多场景下,单例的使用一般是为了使用一些资源比如文件系统、数据库连接等等,这中场景下,一般我们尽量使得该单例必须使用的时候,才会初始化,以避免资源的浪费使用。而且饿汉式单例也没提供异常的处理机制。


Static block initialization
   静态块初始化单例和饿汉式单例相似,差别在于实例的初始化在静态块中,这中方式提供了异常处理。


package com.doctor.design_pattern.singleton;

/**
 * @author sdcuike
 *
 *         Created on 2016年8月1日 上午12:30:08
 */
public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;

    public static StaticBlockSingleton getInstance() {
        return instance;
    }

    static {
        try {
            instance = new StaticBlockSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }

    public static void main(String[] args) {
        System.out.println(StaticBlockSingleton.getInstance());
        System.out.println(StaticBlockSingleton.getInstance());

        System.out.println(StaticBlockSingleton.getInstance() == StaticBlockSingleton.getInstance());

        // com.doctor.design_pattern.singleton.StaticBlockSingleton@2a139a55
        // com.doctor.design_pattern.singleton.StaticBlockSingleton@2a139a55
        // true

    }

}

 

Lazy Initialization
懒初始化方法承担了单例的创建,并且是获取该单例的入口点。


package com.doctor.design_pattern.singleton;

/**
 * @author sdcuike
 *
 *         Created on 2016年8月1日 上午12:39:40
 */
public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;

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

        return instance;
    }

    private LazyInitializedSingleton() {
    }

    public static void main(String[] args) {
        System.out.println(LazyInitializedSingleton.getInstance() == LazyInitializedSingleton.getInstance());// true
    }

}

 

 上面几种单例的实现在单线程环境下可以很好的工作,但可能面临多线程安全问题。


Thread Safe Singleton
  线程安全单例,简单的线程安全我们可以用
synchronized实现。


package com.doctor.design_pattern.singleton;

/**
 * @author sdcuike
 *
 *         Created on 2016年8月1日 上午11:19:36
 *
 *         Thread Safe Singleton
 *
 */
public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

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

    private ThreadSafeSingleton() {

    }

    public static void main(String[] args) {
        System.out.println(ThreadSafeSingleton.getInstance() == ThreadSafeSingleton.getInstance());
        // true
    }

}

上面的线程安全锁的粒度是比较大了,锁的粒度越大,并发性约不能,导致性能下降,我们可以用double checked locking 规则减少锁的粒度。
  


package com.doctor.design_pattern.singleton;

/**
 * @author sdcuike
 *
 *         Created on 2016年8月1日 上午11:37:25
 *
 *         double checked locking principle
 */
public class DoubleCheckedLockingThreadSafeSingleton {

    private static DoubleCheckedLockingThreadSafeSingleton instance;

    public static DoubleCheckedLockingThreadSafeSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingThreadSafeSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingThreadSafeSingleton();
                }
            }
        }

        return instance;
    }

    private DoubleCheckedLockingThreadSafeSingleton() {

    }

    public static void main(String[] args) {

    }

}

 


Inner static helper class Singleton
 采用内部类来实现资源的懒加载:
package com.doctor.design_pattern.singleton;

/**
 * @author sdcuike
 *
 *         Inner static helper class Singleton
 *
 *
 *         Created on 2016年8月1日 上午11:54:05
 */
public class InnerStaticHelperClassSingleton {
    private InnerStaticHelperClassSingleton() {

    }

    public static InnerStaticHelperClassSingleton getInstance() {
        return SingletonHelper.instance;
    }

    private static class SingletonHelper {

        private static final InnerStaticHelperClassSingleton instance = new InnerStaticHelperClassSingleton();
    }

    public static void main(String[] args) {

    }

}

 内部类拥有外部类的实例,当外部类被jvm加载的时候,内部类没有被加载,外部类的实例也就没有被实例化,当外部类真正的调用得到单例入口方法的时候,才会触发内部类的加载,同时外部类的单例也就初始化一次。
 这种方式的好处就是不用锁同步,就可以实现线程安全的单例。


枚举单例实现Enum Singleton
 上面的单例实现可能由于反射导致不能保证一个类只有一个实例。下面的枚举实现方式克服了这个问题,但这种实现放手不能延迟加载。


package com.doctor.design_pattern.singleton;

/**
 * @author sdcuike
 *
 *         Enum Singleton
 *         枚举实现单例不能延迟加载资源,但保证了enum值只实例化一次。而且克服了反射带来的问题
 *
 *         Created on 2016年8月1日 下午12:24:06
 */
public enum EnumSingleton {
    instance;
    private EnumSingleton() {

    }

    public void doSomething() {
        System.out.println("test do ");
    }

    public static void main(String[] args) {
        EnumSingleton.instance.doSomething();
    }

}


序列化与单例
 我们一般通过网络交互的时候都会用到序列化,序列化打破了单例的规则,反序列化我们得到了另一个实例(请自行验证)。为了保证序列化不影响单例规则,我们一般实现以下方法:


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

热门栏目