单例模式介绍

1、单例介绍
1.1、单例模式使用场景

单例模式(Singleton Pattern )是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛。J2EE标准中的ServletContext ,ServletContextConfig 等、 Spring 框架应用中ApplicationContext数据库的连接池等也都是单例形式。

1.2、单例的实现思路
  • 静态化实例对象。
  • 私有化构造方法,禁止通过构造方法创建实例。
  • 提供一个公共的静态方法,用来返回唯一实例。
2、饿汉式单例
2.1、特点

饿汉式单例在类加载的时候就初始化创建对象。线程安全,但是占用、浪费内存。

2.2、标准饿汉式单例代码
public class HungrySingleton {

    // 类初始化的时候就创建对象
    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return  hungrySingleton;
    }
}
2.3、静态代码块饿汉式单例

这种方式和直接使用静态属性的方式差异不大。可以说没有什么差异。只是利用静态代码块初始化对象。

public class HungryStaticSingleton {
    //先静态后动态
    //先上,后下
    //先属性后方法
    private static final HungryStaticSingleton hungrySingleton;

    static {
        hungrySingleton = new HungryStaticSingleton();
    }

    private HungryStaticSingleton(){}

    public static HungryStaticSingleton getInstance(){
        return  hungrySingleton;
    }
}
3、懒汉式单例
3.1、特点

在单例对象被调用的时候才初始化,不浪费内存,但是存在线程安全的问题。

3.2、标准懒汉式

通过加synchronizedd 的方式解决线程安全问题,降低了效率。

public class LazySimpleSingletion {
    private static LazySimpleSingletion instance;
    private LazySimpleSingletion(){}

    public synchronized static LazySimpleSingletion getInstance(){
        if(instance == null){
            instance = new LazySimpleSingletion();
        }
        return instance;
    }
}
3.3、双重检查方式(double check)

LazyDoubleCheckSingleton还没有创建的时候,如果存在多条线程调用getInstance()方法,就会出现并发的问题。所以这里两条线程都会走到synchronized同步代码块里面,这样再来一次判断,保证了LazyDoubleCheckSingleton只会被创建一次。

LazyDoubleCheckSingleton创建以后,这时候不管有多少线程来访问getInstance()方法,通过第一次判断就获得LazyDoubleCheckSingleton对象,就不需要再获取锁,所以对比标准模式提高了执行效率

public class LazyDoubleCheckSingleton {
    // volatile 防止指令重排序的问题
    private volatile static LazyDoubleCheckSingleton instance;
    private LazyDoubleCheckSingleton(){}

    public static LazyDoubleCheckSingleton getInstance(){
        //检查是否要阻塞
        if (instance == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                //检查是否要重新创建实例
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

如果不明白为什么要加volatile关键字,戳这里

4、最牛单例

这个单例是基于懒汉式模式,通过静态内部类的特性(不会自动初始化。只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类)解决线程安全和内存浪费的问题。java类加载顺序

public class LazyStaticInnerClassSingleton {

    // 构造方法私有,只能通过统一入口获取对象
    private LazyStaticInnerClassSingleton(){
        // 这里防止通过反射强制访问创建对象
        if(LazyHolder.INSTANCE != null){
            throw new RuntimeException("不允许非法访问");
        }
    }

    // 提供唯一的访问入口,static修饰不会被重写
    private static LazyStaticInnerClassSingleton getInstance(){
        return LazyHolder.INSTANCE;
    }

    // 通过静态内部类的方式,实现再对象被调用到的时候才创建
    private static class LazyHolder{
        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
    }
}
5、破坏单例的方法
5.1、反射破坏单例

通过反射的方式,强制访问构造方法,创建对象。

public static void main(String[] args) {
    try {
        Class<?> clazz = LazyStaticInnerClassSingleton.class;
        // 通过反射获取私有构造函数
        Constructor c = clazz.getDeclaredConstructor(null);
        // 强制访问
        c.setAccessible(true);
        // 暴力初始化
        Object instance1 = c.newInstance();
        // 再次暴力初始化
        Object instance2 = c.newInstance();
        System.out.println(instance1 == instance2); // false
    }catch (Exception e){
        e.printStackTrace();
    }
}

反射破坏单例的方式,可以通过最牛单例中的如下代码解决

// 构造方法私有,只能通过统一入口获取对象
private LazyStaticInnerClassSingleton(){
    // 这里防止通过反射强制访问创建对象
    if(LazyHolder.INSTANCE != null){
        throw new RuntimeException("不允许非法访问");
    }
}
5.2、序列化破坏单例

例如,可以通过如下代码来破坏单例

// 单例类
public class SerializableSingleton implements Serializable {

    public  final static SerializableSingleton INSTANCE = new SerializableSingleton();
    private SerializableSingleton(){}

    public static SerializableSingleton getInstance(){
        return INSTANCE;
    }
}


// 测试类
public class SerializableSingletonTest {
    public static void main(String[] args) {

        SerializableSingleton s1 = null;
        SerializableSingleton s2 = SerializableSingleton.getInstance();

        FileOutputStream fos = null;
        try {
			// 序列化
            fos = new FileOutputStream("SerializableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            // 反序列化
            FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SerializableSingleton)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);
			/**
			 * 输出结果
             * com.gupaoedu.vip.pattern.singleton.seriable.SeriableSingleton@5fe5c6f
             * com.gupaoedu.vip.pattern.singleton.seriable.SeriableSingleton@5fe5c6f
             * false
             */
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

预防方法,可以通过添加readResolve()方法即可,如下代码测试

// 单例类
public class SerializableSingleton implements Serializable {

    public  final static SerializableSingleton INSTANCE = new SerializableSingleton();
    private SerializableSingleton(){}

    public static SerializableSingleton getInstance(){
        return INSTANCE;
    }
    
    // 添加此方法后,反序列化后的对象和序列化之前的对象是一个对象
    private Object readResolve(){ return INSTANCE;}
}


// 测试类
public class SerializableSingletonTest {
    public static void main(String[] args) {

        SerializableSingleton s1 = null;
        SerializableSingleton s2 = SerializableSingleton.getInstance();

        FileOutputStream fos = null;
        try {
			// 序列化
            fos = new FileOutputStream("SerializableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            // 反序列化
            FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SerializableSingleton)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);
			/**
			 * 输出结果
             * com.gupaoedu.vip.pattern.singleton.seriable.SeriableSingleton@5fe5c6f
             * com.gupaoedu.vip.pattern.singleton.seriable.SeriableSingleton@5fe5c6f
             * true
             */
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里通过添加readResolve()解决了单例的问题,但是单例还是实例化了两次,只是第二次返回的是第一次的地址。

6、注册式单例

注册式单例又称登记式单例,是把每个实例对象登记到某个地方,通过唯一标识获取实例。注册式单例模式有两种,一种枚举单例模式,另一种容器式单例模式

6.1、枚举式单例模式

枚举单例的代码示例

public enum EnumSingleton {
    INSTANCE;

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance(){return INSTANCE;}
}

我个人对枚举单例的使用理解不太透彻,如下单例改造成枚举单例的案例仅供参考:

// 正常单例
public class PoolWorker {

	private final static Logger log = LoggerFactory.getLogger(PoolWorker.class);

	// 构造函数私有
	private PoolWorker() {
		if(InitPoolWorker.poolWorkers != null){
			throw new RuntimeException("PoolWorker has been created!");
		}
	}

	// 唯一访问入口
	public static ExecutorService getPoolWorkers() {
		return InitPoolWorker.poolWorkers;
	}

	// 通过静态内部类的方式初始化,被调用的时候才初始化
	private static class InitPoolWorker{
		private static ExecutorService poolWorkers = new ThreadPoolExecutor(
				10,
				20,
				1000,
				TimeUnit.MILLISECONDS,
				new ArrayBlockingQueue<>(5),
				(Runnable r) -> {
					log.info("线程{}创建",r.hashCode());
					Thread th = new Thread(r,"threadPool"+r.hashCode());
					return th;
				},
				new ThreadPoolExecutor.CallerRunsPolicy());
	}

}
// 改造后枚举单例
public enum PoolWorker2 {
    INSTANCE;
    private final static Logger log = LoggerFactory.getLogger(PoolWorker.class);
    // 唯一访问入口
    public static ExecutorService getPoolWorkers() {
        return InitPoolWorker.poolWorkers;
    }

    // 通过静态内部类的方式初始化,被调用的时候才初始化
    private static class InitPoolWorker{
        private static ExecutorService poolWorkers = new ThreadPoolExecutor(
                10,
                20,
                1000,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(5),
                (Runnable r) -> {
                    log.info("线程{}创建",r.hashCode());
                    Thread th = new Thread(r,"threadPool"+r.hashCode());
                    return th;
                },
                new ThreadPoolExecutor.CallerRunsPolicy());
    }
}

在我看来,单例枚举的使用,仅仅是通过枚举的特性,解决正常单例会出现的反射序列化破坏单例的问题。但是,在使用的时候还是通过通过静态内部类单例或者double check 的方式来实现。

6.2、容器式单例模式

容器式单例模式,适用于需要创建大量单例对象的场景,便于管理。在spring的IOC中也使用到。

public class ContainerSingleton {

    private ContainerSingleton(){}

    private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();

    public static synchronized Object getInstance(String className){
        Object instance = null;
        if(!ioc.containsKey(className)){
            try {
                instance = Class.forName(className).newInstance();
                ioc.put(className, instance);
            }catch (Exception e){
                e.printStackTrace();
            }
            return instance;
        }else{
            return ioc.get(className);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值