目录
浅谈Java中单例模式的几种应用
第一种:懒汉式
第二种:饿汉式
第三种:双重检索式
第四种:注册登记式
第五种:内部类形式
日常开发中,为了提高我们系统中对象的复用性,大多采用单例模式的写法,以达到在系统中重复利用对象的目的。下面小编为大家简单介绍几种日常开发中常见的单例模式写法,以供参考和使用!如果有疑问大家可以留言公共讨论,共同学习进步。
懒汉的方式正如字面意思,可以通俗的理解为:来活了我再开始干活!下面看下懒汉式的具体写法。
public class LazySingletonPattern {
/**
* 私有对象
*/
private static UserModel userModel;
/**
* 获取唯一实例
*
* @return
*/
public static synchronized UserModel getInstance() {
if (userModel == null) {
userModel = new UserModel();
}
return userModel;
}
}
从上面代码分析得知,程序加载时userModel对象是空的,当我们调用getInstance方法时,首先判断内存中是否有userModel对象,如果没有则创建一个,之后静态的userModel对象会一直驻留在外面本机的内存中。需要注意的是这里一定要加上synchronized关键字,来保证getInstance方法是线程安全的,否则可能导致的结果就是内存中会创建多个userModel对象。
饿汉式相对于其他方式是最简单也是最暴力的一种方式,如下代码:
public class HungrySingletonPattern {
/**
* 初始化静态UserModel
*/
private static UserModel userModel = new UserModel();
/**
* 唯一实例
* @return
*/
public static UserModel getInstance(){
return userModel;
}
}
代码中直接实例化UserModel对象,也就是在项目启动的时候就为我们创建好了一份userModel对象,这样的优点在于静态对象本身就是单例的,我们在使用的时候可以不考虑线程安全问题。缺点也是显而易见的,在程序初始化时就要为我们创建好这些对象放入到内存中,造成了空间的浪费。
双重检索的方式,可以说是在性能和安全两个角度找了一个平衡点,可以理解为懒汉式单例模式的加强版,既考虑性能又考虑安全性的问题。
public class DoubleCheckSingletonPattern {
/**
* 静态UserModel
*/
private static UserModel userModel;
/**
* 双重检索单例方式
*
* @return
*/
public static UserModel getInstance() {
if (userModel == null) {
synchronized (DoubleCheckSingletonPattern.class) {
if (userModel == null) {
userModel = new UserModel();
}
}
}
return userModel;
}
}
双重检查锁的优势就在于会优先验证一次userModel对象是否存在值,而不是像懒汉式一样优先加锁,这样导致了性能的浪费。虽说synchronized在1.6之后得到了很好的优化,但是在多线程竞争下依然不排除性能的浪费。双重检索可以降低锁的浪费,也同时保证了线程的安全。
隐患补充:这里隐藏着一个问题,当两个线程进来后,先获取锁的线程开始创建线程的同时,第二个线程进入判断,因为构造对象的过程可能会比较长,这时第一个线程还未完成对象的完整创建,但第二个线程会拿到一个不是null的值,从而认为已经构造完成,导致返回的类并非是一个完整的对象。解决方案可以通过volatile来解决这个问题,完美的解决了这个问题的还是推荐使用内部类的创建方式。
注册登记式更适合多实例场景的管理,当下最火的Spring框架中IOC容器就采用的这种方式来管理我们系统中的Bean对象,小编这里只做简单的示例,相对于Spring中的要简单的多,不喜勿喷。
public class RegistrySingletonPattern {
/**
* Map容器
*/
private final static Map objectsMap = new ConcurrentHashMap<>();
/**
* 获取实例
*
* @return
*/
public static synchronized Object getInstance() {
String key = "&userModel";
if (!objectsMap.containsKey(key)) {
objectsMap.put(key, new UserModel());
}
return objectsMap.get(key);
}
}
上面代码得知我们创建的单例对象全部保存在Map容器中,由Map容器统一管理我们的单例对象。
内部类可以说是比较有创意的一种方式了,避免资源的直接浪费,也同时保证了单例。
public class InnerClassSingletonPattern {
static {
System.out.println("父类加载了");
}
/**
* 获取实例
*
* @return
*/
public static UserModel getInstance() {
return UserModelSingleton.userModel;
}
/**
* 内部类
*/
static class UserModelSingleton {
static final UserModel userModel = new UserModel();
static {
System.out.println("子类加载了");
}
}
}
内部类不同于饿汉式和懒汉式,也算是集成了这两种方式的优点,程序启动后并不会初始化内部类,而当外部类调用内部类的方法时,才会初始化内部类的UserModel实例,保证在不浪费资源的情况下达到的单例模式的应用。
小结:
小编这里只是例举了几种在工作中常用的几种写法,有问题的地方还请大家及时指出,免的误导了其他同学。本文涉及到其他技术点,这里不做扩展讲解,大家可以去了解下多线程多面的知识,小编也还有许多需要学习的地方,还请大家多多指点。