您的位置:首页 > 编程语言 > Java开发

JAVA 的几种单例模式以及优缺点

2016-05-03 20:56 567 查看
HeadFirst里面的单例模式的简要定义是:确保一个类只有一个实例,并提供一个全局访问点。先来看一下几种常见的单例模式。

1.懒汉模式

懒汉模式意思就是延迟加载,等到要用这个类了,才会去加载实例。

public class JiaJia {

private static JiaJia instance;

private JiaJia() {
}

public static JiaJia getInstance() {

if (instance == null) {
instance = new JiaJia();
}

return instance;
}

}


 这种到要用的时候才会去加载,延迟加载,但是存在线程不安全的问题。

可以改进一下,加个同步锁,可以解决线程安全的问题。

public class JiaJia {

private static JiaJia instance;

private JiaJia() {
}

public synchronized static JiaJia getInstance() {

if (instance == null) {
instance = new JiaJia();
}

return instance;
}

}
这种方式的问题也显而易见,每次获取instance的时候都会去加锁解锁,效率也自然而然的降低了。所以可以再改进一下。采用双重检查加锁。
public class JiaJia {

private static JiaJia instance;

private JiaJia() {
}

public static JiaJia getInstance() {

if (instance == null) {
synchronized (JiaJia.class) {
if (instance == null) {
instance = new JiaJia();
}
}
}

return instance;
}

}
这种方式解决了线程安全的问题,也解决了效率的问题。但是写起来比较麻烦,代码有点冗长,在instance = new JIaia();的时候,还是可能存在问题。这就要去了解创建对象的过程了。当new的时候,会发生以下事情,1.类加载检查;2.分配内存,把内存空间初始化为零值。3.调用构造函数。实际上一个类在2步骤的时候,就已经为非null了,所以当线程1执行完2步骤,还未执行3步骤的时候,线程2刚好判断instance不为null,就返回报错了。针对这个问题,也有一个解决方法,加入一个volatile关键字。
private volatile static JiaJia instance; //volatile关键字可以使变量不从寄存器中读取,而是从内存中重新读取他的值


2.饿汉模式

饿汉模式跟懒汉模式相反,饿汉模式是当加载这个类的时候,就去创建这个单例。下面的这种单例方法也是最常用的方法。

public class JiaJia {

private static final JiaJia instance = new JiaJia();

private JiaJia() {
}

public static JiaJia getInstance() {
return instance;
}

}


3. 几点补充

静态内部类,推荐使用

public class JiaJia {

private JiaJia() {
}

private static class JiaJiaHolder {

private static final JiaJia instance = new JiaJia();

}

public static JiaJia getInstance() {
return JiaJiaHolder.instance;
}

}
由于内部类是static的,所以保证了线程安全的问题。然后getInstance的时候,再去创建实例,也实现了延迟加载。所以这种方法比较完美。

枚举类型实现单例

枚举类型实现单例是实现的最简单的做法,用的人也比较少,但是是实现单例的最佳方法。

public enum JiaJia {
INSTANCE;
}
《Effective Java》中说到枚举类型的好处在于无偿的提供了反序列化机制,并且有效的抵御了反射机制的攻击。这里提到了两个问题,1.反序列化;2.反射机制攻击

1.反序列化。除了枚举类型,其他的单例模式都会有一个问题,就是当这个类要变成可序列化,也就是在声明中加上了implements Serializable的时候,单例模式就会失效,因为readObject方法总会创建一个新的对象。但是也有解决方法,定义一个readResolve方法,该方法的作用是在反序列化之后,readResolve会被调用,该方法返回的对象就会替换新建的对象,而新建的对象会被垃圾回收,从而保证了单例。

private Object readResolve() {
return INSTANCE;
}
2.反射机制攻击。

利用反射机制来调用private的构造方法,借助Constructor.setAccessible方法可以使一个私有构造方法再次被调用。要抵御住这种攻击,可以修改构造器,在创建第二个实例的时候抛出异常。

private static boolean isCreated;

private JiaJia() {
if (isCreated) {
throw new RuntimeException("已经创建了实例");
}
isCreated = true;
}


当然,在枚举类型的单例模式中是不存在以上两个问题的,所以在《Effective Java》中说到,虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为了实现Singleton的最佳方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息