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

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


单例模式的定义:

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

  饿汉式单例存在多线程安全问题,我们可以看一下多线程问题,为了模拟,在饿汉式得到实例的时候加入随机休眠时间:

/**
 * @author sdcuike
 *
 *         Lazy initialization method to implement Singleton pattern creates the
 *         instance in the global access method. Here is the sample code for
 *         creating Singleton class with this approach. Created on 2016年8月1日
 *         上午12:39:40
 * 
 * @see http://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples
 *      懒初始化,多线程会导致破坏单例原则
 */
public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;

    /**
     * 为了模拟多线程情况下的问题,加入了休眠时间
     * 
     * @return
     */
    public static LazyInitializedSingleton getInstance() {
        try {
            Thread.sleep((long) (Math.random() * 100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (instance == null) {
            try {
                Thread.sleep((long) (Math.random() * 100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazyInitializedSingleton();
            try {
                Thread.sleep((long) (Math.random() * 100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return instance;
    }

    private LazyInitializedSingleton() {
    }

    public static void main(String[] args) {
        // System.out.println(LazyInitializedSingleton.getInstance() ==
        // LazyInitializedSingleton.getInstance());// true
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                System.out.println(LazyInitializedSingleton.getInstance());
            });
        }

        executorService.shutdown();
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@6a1bb74
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@6a1bb74
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@6a1bb74
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@2be16806
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@2be16806
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@5f93f536
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@5f93f536
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@5f93f536
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@5f93f536
        // com.doctor.design_pattern.singleton.LazyInitializedSingleton@46d3a9
        // 得到的实例并不是同一个。
    }

}

 执行结果也附在上面了,可以得知,多线程环境下,懒初始化单例实现存在风险,会得到不止一实例,主要问题是方法中修改的是类变量,多线程环境下存在并发访问问题。

  线程安全单例,简单的线程安全我们可以用
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) {

    }

}

 double check的目的是为了进步一获得线程安全,第一步check为null的时候可能先后进来两个线程访问,此时单例还没初始化,这个时候开始上锁,如果前一个线程上锁后初始化了这个单例而释放锁,那后边的线程也可以锁定对象,然后初始化,这样就违背了单例原则,所以我们在锁住对象后又进一步check对象是否初始化了。

不过,由于java内存的可见性问题(java内存模型),一个线程初始化了对象,double check的时候,并不一定能时时判断实例是否被另一个线程初始化了,所以上面的代码还是有线程安全问题的,所以我们必须在实例变量声明的时候加上关键字volatile,使得多线程对变量的修改能时时的让其他线程看到这个结果。
 所以,最终的double check单实例代码应该是这样的:
/**
 * @author sdcuike
 *
 *         Created on 2016年8月1日 上午11:37:25
 * 
 *         double checked locking principle and volatile variable
 */
public class DoubleCheckedLockingThreadSafeSingleton {

    private static volatile DoubleCheckedLockingThreadSafeSingleton instance;

    /**
     * double check的目的是为了进步一获得线程安全,第一步check为null的时候可能先后进来两个线程访问,此时单例还没初始化,
     * 这个时候开始上锁,如果前一个线程上锁后初始化了这个单例而释放锁,那后边的线程也可以锁定对象,然后初始化,
     * 这样就违背了单例原则,所以我们在锁住对象后又进一步check对象是否初始化了。
     * 不过,由于java内存的可见性问题(java内存模型),一个线程初始化了对象,double
     * check的时候,并不一定能时时判断实例是否被另一个线程初始化了,所以上面的代码还是有线程安全问题的,
     * 所以我们必须在实例变量声明的时候加上关键字volatile,使得多线程对变量的修改能时时的让其他线程看到这个结果。
     * 
     * @return
     */
    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

 枚举能够确保enum value只有一次实例化,而且enum value全局可访问,所以也是单例的实现方式之一。但这种实现方式不够灵活,而且不能延迟加载。

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();
    }

}

序列化与单例

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


反射与单例

拿饿汉式单例实现来模拟,我们用反射实例化此单例带来的后果就是,违反了单例的原则,反射能实例化另一个对象,而不是之前的实例对象。


 public static void main(String[] args) throws ReflectiveOperationException, SecurityException {
        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

        // 反射破坏
        Constructor<EagerInitializedSingleton> constructor = EagerInitializedSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        EagerInitializedSingleton newInstance = constructor.newInstance();
        System.out.println(newInstance);
    }

执行结果:

com.doctor.design_pattern.singleton.EagerInitializedSingleton@15db9742
com.doctor.design_pattern.singleton.EagerInitializedSingleton@15db9742
test
true
com.doctor.design_pattern.singleton.EagerInitializedSingleton@6d06d69c

由于没有重写toString方法,我们可以根据后面的hashcode值判断是不是相同的对象。
为了防止反射破坏单例规则,我们对私有构造函数处理:

 /**
     * 反射导致单例失败:How to fix: Throw Runtime Exception if someone tries to make
     * instance in case one instance already exists. This code will go into the
     * private constructor of the Singleton class
     */
    private EagerInitializedSingleton() {
        if (instance != null) {
            throw new RuntimeException("plean use  getInstance() method");
        }
    }


反射实例化对象会导致失败:
com.doctor.design_pattern.singleton.EagerInitializedSingleton@15db9742
com.doctor.design_pattern.singleton.EagerInitializedSingleton@15db9742
test
true
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.doctor.design_pattern.singleton.EagerInitializedSingleton.main(EagerInitializedSingleton.java:57)
Caused by: java.lang.RuntimeException: plean use  getInstance() method
	at com.doctor.design_pattern.singleton.EagerInitializedSingleton.<init>(EagerInitializedSingleton.java:31)
	... 5 more



Cloning 与单例

  实例化对象的一种方式就是通过克隆。当我们克隆单例对象的时候,我们也违反了单例原则,拿饿汉式单例来说:

public class EagerInitializedSingleton implements Cloneable {
    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

    /**
     * 反射导致单例失败:How to fix: Throw Runtime Exception if someone tries to make
     * instance in case one instance already exists. This code will go into the
     * private constructor of the Singleton class
     */
    private EagerInitializedSingleton() {
        if (instance != null) {
            throw new RuntimeException("plean use  getInstance() method");
        }
    }

    public static EagerInitializedSingleton getInstance() {
        return instance;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public static void main(String[] args) throws ReflectiveOperationException, SecurityException, CloneNotSupportedException {
        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

        // 反射破坏
        // Constructor<EagerInitializedSingleton> constructor =
        // EagerInitializedSingleton.class.getDeclaredConstructor();
        // constructor.setAccessible(true);
        // EagerInitializedSingleton newInstance = constructor.newInstance();
        // System.out.println(newInstance);

        // 克隆破坏
        EagerInitializedSingleton eagerInitializedSingleton = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton eagerInitializedSingletonClone = (EagerInitializedSingleton) eagerInitializedSingleton.clone();
        System.out.println(eagerInitializedSingleton + "==" + eagerInitializedSingletonClone + " is " + (eagerInitializedSingleton == eagerInitializedSingletonClone));
    }

}


执行结果:
克隆破坏:
com.doctor.design_pattern.singleton.EagerInitializedSingleton@15db9742==com.doctor.design_pattern.singleton.EagerInitializedSingleton@6d06d69c is false

为了防止克隆破坏,和反射一样,抛出异常:

 @Override
    protected Object clone() throws CloneNotSupportedException {
        if (instance != null) {
            throw new CloneNotSupportedException("plean use  getInstance() method");
        }
        return super.clone();
    }


代码:


参考:

http://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples
JAVA  DESIGN  PATTERNS  By  Devendra Singh @Copyright 2016 Dev S



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dreamer who

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值