vlambda博客
学习文章列表

比想象中复杂一点的单例模式

单例模式是指在整个软件系统中,某个类只能产生一个对象实例,并且只有一个提供对象实例的方法。

很多人都说单例模式是最简单的一种设计模式,但是单例模式实际上有7种写法,很多人在面试的过程中会踩坑。接下来直接上代码。

一、饿汉(静态方法
class B{ //1.私有构造方法 private B(){} //2.静态私有对象 private final static B b = new B(); //3.提供一个对外访问的方法 public static B getInstance(){ return b; }}
优点: 在类装载的时候创建一次实例,避免了线程同步的问题
缺点:在类装载的时候就创建实例,因为类装载的原因有很多种,不一定是调用 getInstance方法才装载,没有达到懒加载的原则,如果从始至终没有使用过这个实例,就会造成内存浪费。
结论:这种方式可以用,但是有可能会造成内存浪费,不过大多数实际开发问题都不大。

二、 饿汉 (静态代码块
class B{ //1.私有构造方法 private B(){} //2.静态私有对象 private static B b ; static { b = new B(); } //3.提供一个对外访问的方法 public static B getInstance(){ return b; }}
饿汉 (静态代码块 的写法和 饿汉 (静态方法 的写法略有不同,但是他们的优缺点是一样的,仅仅是写法不一样。

三、懒汉式(线程不安全)
class B{ //1.私有构造方法 private B(){} //2.静态私有对象 private static B b ; //3.提供一个对外访问的方法 public static B getInstance(){ if(b == null){ b = new B(); } return b; }}
优点:起到了lazy loading的效果
缺点:在多线程的情况下,多个线程同时走到if(b==null)这一步有可能都返回true,于是就创建了多个实例。
结论:在实际开发中不要用。

四、懒汉式(线程安全,同步方法)
class B{ //1.私有构造方法 private B(){} //2.静态私有对象 private static B b ; //3.提供一个对外访问的方法 public synchronized static B getInstance(){ if(b == null){ b = new B(); } return b; }}
这种方法是对 懒汉式(线程不安全)这种方法的一种优化,但是解决这个问题的同时又产生了另外的问题,即在多线程的情况下效率略低。
结论:不推荐使用这种方法。

五、双重检测
class B{ //1.私有构造方法 private B(){} //2.静态私有对象 private static volatile B b ; //3.提供一个对外访问的方法 public static B getInstance(){ if(b == null){ synchronized (B.class){ if(b == null){ b = new B(); } } } return b; }}
如上代码,对静态实例变量b加 volatile 修饰,多线程环境下就可以达到及时通知的目的,我们在获取实例的方法中加两个判空和一个同步,基本上达到了线程安全和效率的平衡点。
结论:推荐使用

六、静态内部类实现
class B{ //1.私有构造方法 private B(){} public static class C{ public static B b = new B(); } //2.提供一个对外访问的方法 public static B getInstance(){ return C.b; }}
如上代码:我们通过静态内部类C,在C中实例化对象B,再通过 getInstance获取到B的实例,因为类在装载的时候不会装载内部类,只有在使用 getInstance的时候才会装载,而且只会装载一次,而且不会产生线程问题。
结论:推荐使用

七、枚举实现
enum B{ INSTANCE; public void print(){ System.out.println("HELLO WORLD"); }}
public class A { public static void main(String[] args) { B instance1 = B.INSTANCE; B instance2 = B.INSTANCE; System.out.println(instance1 == instance2); System.out.println("instance1:"+instance1.hashCode()); System.out.println("instance2:"+instance2.hashCode()); instance1.print(); }}

如上述代码和截图,可以看到 instance1和instance2的hascode都是一样的,足可以证明两个实例其实是一个,并且可以成功的调用到枚举类中的方法。

这是通过JDK1.5中的枚举来实现单例模式,能够避免多线程的问题,也是Effective java的作者提倡的方式。

在你的实际开发中,用到单例模式的地方多吗?