英文名:Singleton Pattern。英文原话:Ensure a class has only one instance,and provide a global point of access to it。
单例模式的主要作用是确保一个类只有一个实例。
一、实现方式
1.静态内部类
这是最好的实现方式,废话不多说,直接上代码。
要点:私有的构造方法,保证外界无法直接实例化。一个内部静态类来进行实例化,这样能达到延迟初始化的效果。并且有 classloader 机制来保证初始化 instance 时只有一个线程。
public class Singleton { //构造方法私有,保证外界无法直接实例化 private Singleton(){} public static Singleton getInstance(){ return SingletonInstance.instance; } private static class SingletonInstance{ static Singleton instance = new Singleton(); } }
2.饿汉模式
public class HungrySingleton { private HungrySingleton() {} private static HungrySingleton instance = new HungrySingleton(); private static HungrySingleton getInstance() { return instance; } }
饿汉模式是在类加载的时候,就进行对象实例化。缺点就是如果这个类里面有一个静态方法,代码里先使用了这个静态方法,这时候就会完成实例化。这时候可能是还用不到这个实例的,就会造成浪费。就这一个问题,所以结合具体情况,饿汉模式使用也没有大问题。
3.懒汉模式
public class LazySingleton { private static LazySingleton instance; private LazySingleton() { } public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }
懒汉模式是在第一次引用类时,才进行对象实例化。这种方式存在的问题是线程安全问题,就是多个线程同时去获取实例的时候,可能会产生多个实例。
讲到线程安全问题,通常就会想到synchronized加一个锁来保证线程安全问题,没错,如下图,可以这样来保证线程安全
public class OneCheckLazySingleton { private static OneCheckLazySingleton instance; private OneCheckLazySingleton() { } public static synchronized OneCheckLazySingleton getInstance() { if (instance == null) { instance = new OneCheckLazySingleton(); } return instance; } }
但是这样每次去获取实例的时候,都会去走一遍这个锁,其实在实例已经创建过后,就不会有线程安全问题了。所以有了改进版,双重检查锁,如下
public class DoubleCheckLazySingleton { private volatile static DoubleCheckLazySingleton singleton; private DoubleCheckLazySingleton() { } public static DoubleCheckLazySingleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new DoubleCheckLazySingleton(); } } } return singleton; } }
这样在实例已经创建后,再去获取就不会走锁了。注意这里的volatile关键字,volatile禁止了JVM自动的指令重排序优化,是在jdk1.5及以后才有的。这样,这种方式也是没有问题的了,就是代码相对复杂。
二、具体应用
先讲一个概念,有状态的类和无状态的类。
有状态的类:类里面有成员变量,而且成员变量是会变的。
无状态的类:类里面没有成员变量,或者有成员变量但是不可变的。
无状态的类才适合使用单例模式,一些连接池,比如连接redis的jedis工具的JedisPool 可以使用单例。
再讲一下分布式下的情况,分布式时,就是有多个jvm。一开始想到这个问题时,我还认为在分布式情况下会有问题,因为在一个jvm下有一个单例,整个系统不是只有一个实例了。 但仔细想了一下,因为是无状态的类,对整个系统是没有问题的。
好了,差不多就这些。谢谢阅读~