单例设计模式是23种设计模式中最常用的设计模式之一,无论是三方类库还是日常开发几乎都有单例设 计模式的影子。单例设计模式提供了一种在多线程情况下保证实例唯一性的解决方案。单例设计模式虽然简单,但是实现方案却非常多,大体上有以下7种最常见的方式。
示例:
public class test1 {
/**
* 直接初始化对象
* */
private static final test1 INSTANCE = new test1();
/**
* 不允许外界进行new对象
**/
private test1() {
}
/**
* 放行唯一方法 获取对象
* @return
*/
public static test1 getInstance() {
return INSTANCE;
}
}
总结: 这种方案实现起来最简单,当test1被加载后,就会立即创建instance,因此该方法可以保证百分百的单例,instance不可能被实例化两次。但是这种做instance可能被加载后很长一段时间才会被使用,就意味着instance开辟的内存占用时间更多。
注意: 如果一个类中成员属性比较少,且占用内存资源不多,那么就可以使用饿汉式。如果一个类中都是比较重的资源,这种方式就比较不妥
示例:
public class test2 {
private static test2 instance;
private test2() {
System.out.println("类被实例化了");
}
public static test2 getInstance() {
if (instance == null) {
instance = new test2();
}
return instance;
}
}
总结: 当instance为null时,getInstance会首先去new一个实例,那之后再将实例返回。
注意: 但是这种实现方式会存在线程安全问题,多个线程同时获取将会出现不同的对象实例,破坏了单例的原则。
示例:
public class test2 {
private static test2 instance;
private test2() {
System.out.println("类被实例化了");
}
public static synchronized test2 getInstance() {
if (instance == null) {
instance = new test2();
}
return instance;
}
}
总结: 这种做法就保证了懒加载又能够百分百保证instance是单例的,但是synchronized关键字天生的排他性导致该方法性能过低。
示例:
public class test2 {
private static test2 instance;
private Object o1;
private Object o2;
private test2() {
o1=new Object();
o2=new Object();
System.out.println("类被实例化了");
}
public static test2 getInstance() {
// 为null时,进入同步代码块,同时避免了每次都需要进入同步代码块
if (instance == null) {
// 只有一个线程能够获取到锁
synchronized (test2.class) {
// 如果为Null在创建
if (instance == null) {
instance = new test2();
}
}
}
return instance;
}
}
总结: 当两个线程发现 instance == null 时,只有一个线程有资格进入同步代码块,完成对instance的初始化,随后的线程再次进入同步代码块之后,因为 instance == null 不成立,就不会再次创建,这是未加载情况下并行的场景,而instance加载完成后,再有线程进入getInstance方法后,就直接返回 instance,不会进入到同步代码块,从而提高性能。
注意: 这种做法看似完美和巧妙,既满足懒加载,又保证instance的唯一性,但是这种方式实际上是会出现
空指针
异常的。
在test2构造方法中,我们会初始化 o1 和 o2两个资源,还有Single自身,而这三者实际上并无前后关系的约束,那么极有可能JVM会对其进行重排序,导致先实例化test2,再实例化o1和o2,这样在使用test2时,可能会因为o1和o2没有实例化完毕,导致空指针异常。
private volatile
static test2 instance;
holder方式借助了类加载的特点,我们直接看代码。
public class test3 {
private test3() {
System.out.println("类被实例化了");
}
/**
* 使用内部类方式不会主动加载,只有主类被使用的时候才会进行加载
*/
private static class Holder {
private static test3 instance = new test3();
}
/**
* 提供外界进行调用
* @return
*/
public static test3 getInstance() {
return Holder.instance;
}
}
总结: 我们发现,在test3中并没有instance,而是将其放到了静态内部类中,使用
饿汉式
进行加载。但是实际上这并不是饿汉式。因为静态内部类不会主动加载,只有主类被使用时才会加载
,这也就保证了程序运行时并不会直接创建一个instance而浪费内存,当我们主动引用Holder时,才会创建instance实例
,从而保证了懒加载。
示例:
public enum test4 {
INSTANCE;
test4() {
System.out.println("类被实例化了");
}
public static test4 getInstance() {
return INSTANCE;
}
}