单例模式

什么是单例模式

  一个对象在应用程序中只存在一个。

单例实现原理

  以java为例,私有构造器,一个私有的静态的本身实例,一个对外开放获取本身实例的静态公共方法。

多种不同的方式实现单例

  饿汉式

  在使用这个类时就初始化这个对象。

package com.lxlyq.singlePattern;

/**
 * 饿汉式单例
 */
public class HungrySingle {
    // 私有对象
    private static HungrySingle hungrySingle = new HungrySingle();
    // 私有构造,防止外部new
    private HungrySingle(){}
    // 外部调用
    public static HungrySingle getHungrySingle() {
        return hungrySingle;
    }
}

  缺点:可能没有使用这个对象,但应用程序中也存在这个对象,浪费内存。

  懒汉式

  只有获取这个实例时才初始化对象。

package com.lxlyq.singlePattern;

/**
 * 懒汉式
 */
public class LazySingle {
    // 私有对象 未初始化
    private static LazySingle lazySingle;
    // 私有构造 防止外部通过 new 创建
    private LazySingle() { }
    // 获取实例
    public static LazySingle getInstance() {
        // 不存在则创建
        if (lazySingle == null) {
            lazySingle = new LazySingle();
        }
        return lazySingle;
    }
}

  优点:弥补了饿汉式的不足。

  缺点:当存在多线程的时候将存在线程安全问题,两个线程同时进入if判断语句中,将会存在两个对象。

懒汉式同步方法锁

package com.lxlyq.singlePattern;

/**
 * 懒汉式(同步方法)
 */
public class LazySynchronizedMethodSingle {
    // 私有对象 未初始化
    private static LazySynchronizedMethodSingle lazySingle;
    // 私有构造 防止外部通过 new 创建
    private LazySynchronizedMethodSingle() { }
    // 获取实例 加上同步锁 防止多线程创建多个实例
    public static synchronized LazySynchronizedMethodSingle getInstance() {
        // 不存在则创建
        if (lazySingle == null) {
            lazySingle = new LazySynchronizedMethodSingle();
        }
        return lazySingle;
    }
}

  优点:当获取时才创建实例,不存在线程安全问题。

  缺点:每次获取实例时都需要加锁,效率极低。

懒汉式同步块锁

package com.lxlyq.singlePattern;

/**
 * 懒汉式同步块
 */
public class LazySynchronizedBlockSingle {
    // 私有对象 未初始化
    private static LazySynchronizedBlockSingle lazySingle;
    // 私有构造 防止外部通过 new 创建
    private LazySynchronizedBlockSingle() { }
    // 获取实例 加上同步块防止多线程创建多个实例
    public static  LazySynchronizedBlockSingle getInstance() {
        synchronized (LazySynchronizedBlockSingle.class) {
            // 不存在则创建
            if (lazySingle == null) {
                lazySingle = new LazySynchronizedBlockSingle();
            }
        }
        return lazySingle;
    }
}

  与同步方法锁一样,效率也差不多,每次都需要加锁。需要改进如下

懒汉式双重校验同步块锁

package com.lxlyq.singlePattern;

/**
 * 懒汉式双重校验同步块
 */
public class LazyDoubleCheckSynchronizedBlockSingle {
    // 私有对象 未初始化
    private static LazyDoubleCheckSynchronizedBlockSingle lazySingle;
    // 私有构造 防止外部通过 new 创建
    private LazyDoubleCheckSynchronizedBlockSingle() { }
    // 获取实例 加上同步块防止多线程创建多个实例
    public static LazyDoubleCheckSynchronizedBlockSingle getInstance() {
        if (lazySingle == null) {
            // 第一次加锁,以后都不会再进入
            synchronized (LazyDoubleCheckSynchronizedBlockSingle.class) {
                // 不存在则创建
                if (lazySingle == null) {
                    lazySingle = new LazyDoubleCheckSynchronizedBlockSingle();
                }
            }
        }
        return lazySingle;

    }
}

  优点:只有获取对象时才被创建。不存在线程安全问题。因为双重校验,只有当对象未被创建前才会被加锁,效率满足要求。

  缺点:不符合审美(瞎扯)。

以上的真的都是单例了吗

  // 通过反射破坏

package com.lxlyq.singlePattern;
import java.lang.reflect.Constructor;
public class SingleDemo {
    public static void main(String[] args) throws Exception {
        // 通过懒汉式双重校验获取实例
        LazyDoubleCheckSynchronizedBlockSingle single1 = LazyDoubleCheckSynchronizedBlockSingle.getInstance();
        System.out.println(single1);
        // 通过懒汉式双重校验获取实例
        LazyDoubleCheckSynchronizedBlockSingle single2 = LazyDoubleCheckSynchronizedBlockSingle.getInstance();
        System.out.println(single2);
        // 通过反射创建对象
        Class cls = LazyDoubleCheckSynchronizedBlockSingle.class;
        Constructor<?> cs = cls.getDeclaredConstructor();
        cs.setAccessible(true);
        LazyDoubleCheckSynchronizedBlockSingle single3 = (LazyDoubleCheckSynchronizedBlockSingle) cs.newInstance();
        System.out.println(single3);
        // out 
        // com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@4554617c
        // com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@4554617c
        // com.lxlyq.singlePattern.LazyDoubleCheckSynchronizedBlockSingle@74a14482
    }
}

  之前所有的单例创建方式都存在通过反射破坏单例问题。

解决单例破坏问题

package com.lxlyq.singlePattern;

/**
 * 禁止通过反射创建单例
 */
public class BanReflexCreateSinge {
    // 采用饿汉式,但是客户端一样可以通过反射创建
    private static BanReflexCreateSinge banReflexCreateSinge = new BanReflexCreateSinge();
    // 防止反射
    private BanReflexCreateSinge(){
        if (banReflexCreateSinge != null) {
            throw new RuntimeException("禁止一切方法破坏单例");
        }
    }
    // 外部调用
    public static BanReflexCreateSinge getInstance() {
        return banReflexCreateSinge;
    }
}

  通过这种反射,当系统中存在时就会抛出异常。

  有一种通过枚举方式创建单例,枚举的底层也是这个原理。

  这里没有介绍通过枚举创建单例,是我觉得枚举没有类那么灵活,尽管枚举是创建单例的最好方式,但不是最好的方案。

静态内部内实现单例

package com.lxlyq.singlePattern;

/**
 * 内部类创建单例
 */
public class LazyInnerClassSingle {
    // 防止外部调用
    private LazyInnerClassSingle(){}
    // 静态内部类
    private static class LazyInnerClass{
        private static LazyInnerClassSingle lazyInnerClassSingle = new LazyInnerClassSingle();
    }
    // 获取实例
    public static LazyInnerClassSingle getInstance() {
        return LazyInnerClass.lazyInnerClassSingle;
    }
}

  这种方式,没有加锁,但也是懒汉式,代码简洁,推荐使用。

总结

  单例优点:

  • 系统中只存在一个,减少内存开支,当一个对象需要被频繁创建销毁时,把它作为单例效果将极其明显。
  • 系统多次需要使用一个对象时,把他设为单例可以减少系统开支。(如读取的配置信息)。
  • 单例可以防止资源被多重占用,如读写文件。 

   单例缺点:

  • 不适用于变化的对象
  • 一般单例的职责都过重,违背了'单一原则'(一个类负责一件事)
  • 单例没有抽象层,难以扩展。

  

原文地址:https://www.cnblogs.com/tysonlee/p/11100617.html