JAVA 的几种单例模式以及优缺点
2016-05-03 20:56
567 查看
HeadFirst里面的单例模式的简要定义是:确保一个类只有一个实例,并提供一个全局访问点。先来看一下几种常见的单例模式。
1.懒汉模式
懒汉模式意思就是延迟加载,等到要用这个类了,才会去加载实例。
这种到要用的时候才会去加载,延迟加载,但是存在线程不安全的问题。
可以改进一下,加个同步锁,可以解决线程安全的问题。
2.饿汉模式
饿汉模式跟懒汉模式相反,饿汉模式是当加载这个类的时候,就去创建这个单例。下面的这种单例方法也是最常用的方法。
3. 几点补充
静态内部类,推荐使用
枚举类型实现单例
枚举类型实现单例是实现的最简单的做法,用的人也比较少,但是是实现单例的最佳方法。
1.反序列化。除了枚举类型,其他的单例模式都会有一个问题,就是当这个类要变成可序列化,也就是在声明中加上了implements Serializable的时候,单例模式就会失效,因为readObject方法总会创建一个新的对象。但是也有解决方法,定义一个readResolve方法,该方法的作用是在反序列化之后,readResolve会被调用,该方法返回的对象就会替换新建的对象,而新建的对象会被垃圾回收,从而保证了单例。
利用反射机制来调用private的构造方法,借助Constructor.setAccessible方法可以使一个私有构造方法再次被调用。要抵御住这种攻击,可以修改构造器,在创建第二个实例的时候抛出异常。
当然,在枚举类型的单例模式中是不存在以上两个问题的,所以在《Effective Java》中说到,虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为了实现Singleton的最佳方法。
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的最佳方法。
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序