1 单例模式
特点:
- 单例模式的类只有一个实例化对象;
- 单例模式的对象只能由单例类自行创建;
1.1 懒汉模式
介绍:只有在程序运行时使用的时候采取创建这个单例。
public class LazySingleton {
private static volatile LazySingleton instance = null;
private LazySingleton() {
} //private 避免类在外部被实例化
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
解释两个关键字:
-
volatile
:保证变量在所有线程中同步,避免脏读; -
synchronized
:修饰方法,表明该方法是一个同步方法,某线程正在使用时,其他线程不可以使用;
1.2 饿汉模式
介绍:一旦加载类就创建一个单例,不管后面是不是需要使用到。
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
1.3 双重检查锁机制
懒汉模式的变种加强版,可以提升一些同步机制带来的性能问题。在双重锁机制中,程序首先判断实例对象是否存在,如果存在就不需要同步操作,直接返回对象即可。只有在没有生成单例对象的时候才进入同步代码块进行生成对象的操作。
public class LazySingleton {
private static volatile LazySingleton instance = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
这里解读一下第二次判断的意义,假如实例对象还没有被创建,线程T1调用getInstance通过了第一次判断准备执行同步代码块。这时,线程T2抢到了cpu资源,也去调用了getInstance方法,并且成功的创建了对象。后续的执行过程要防止线程T1继续创建对象,所以就有个第二个判断。
1.4 类级内部类模式
内部类有个特点,程序启动加载其所在外部类的时候并不会同时加载内部类。而只有外部类调用内部类的时候才回去加载内部类。
这样就解决了两个问题:
- 懒汉模式需要做判断,效率低;
- 饿汉模式占用空间;
public class ClassSingleton {
public static class InsideClass {
private static ClassSingleton instance = new ClassSingleton();
}
private ClassSingleton() {
}
public static ClassSingleton getInstance() {
return InsideClass.instance;
}
}
完美吗?其实不。看个例子:
public static void main(String[] args) {
try {
ClassSingleton singleton = ClassSingleton.getInstance();
Constructor<ClassSingleton> constructor = ClassSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
ClassSingleton newSingleton = constructor.newInstance();
System.out.println(singleton == newSingleton);
}catch (Exception e){
e.printStackTrace();
System.out.println(e.getMessage());
}
}
Constructor类用来描述类的构造方法信息;
getDeclaredConstructor()方法可以获取到类的私有构造器;
setAccessible()方法当参数为true时才可以使用私有构造器;
newInstance()方法用来调用构造函数。
上述代码输出是false。
内部类的方式在两种情况也存在问题,
- 上述代码,通过反射机制来创建单例类的对象时;
- 反序列化生成对象时。
反序列化实例:
首先单例类要去实现java.io.Serializable
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
public static void main(String[] args) {
try {
ClassSingleton singleton = ClassSingleton.getInstance();
byte[] serialize = SerializationUtils.serialize(singleton);
ClassSingleton newInstance = SerializationUtils.deserialize(serialize);
System.out.println(singleton == newInstance);
}catch (Exception e){
e.printStackTrace();
System.out.println(e.getMessage());
}
}
输出也为false。
1.5 枚举模式
effective java中提到最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。
写法简单:
public enum EnumSingleton {
INSTANCE;
public void exec(){
System.out.println("开始调用单例方法");
}
}
调用简单:
public static void main(String[] args) {
EnumSingleton.INSTANCE.exec();
}
1.6 注册登记模式
Spring的IOC容器使用的模式,每个实例都缓存到统一容器管理,通过唯一标识获取对应的实例 。
public class ContainerSingleton {
private ContainerSingleton() {}
private volatile static Map<String, Object> ioc = new HashMap<>();
public static Object getInstance(String name) {
Object instance = null;
if (!ioc.containsKey(name)) {
synchronized (ContainerSingleton.class) {
if (!ioc.containsKey(name)) {
try {
instance = Class.forName(name).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
ioc.put(name, instance);
}
}
}
return ioc.get(name);
}
}
总结一下,其实实现单例的方法有很多,适合自己的就是最好的。