单例设计模式
单例模式(Singleton Pattern)是设计模式中比较常用的一种,主要思想是保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式三要点:
(1)、单例类只能有一个实例
这是最基本的,真正做到整个系统中唯一并不容易,通常还要考虑反射破坏、序列化/反序列化、对象垃圾回收等问题。
(2)、单例类必须自己创建自己的唯一实例
通常给实例构造函数protected或private权限。
(3)、单例类必须给所有其他对象提供这一实例
通常定义静态方法getInstance()返回。
饿汉式
public class Hungry {
private static Hungry hungry = new Hungry();
private Hungry() {
}
public static Hungry instance() {
return hungry;
}
}
这种方式不会产生线程安全问题,因为在JVM中,对类的加载和类初始化,由虚拟机保证线程安全。
优点:没有加锁,执行效率会提高。
缺点:可能有时候不需要使用,会浪费内存,如果这个对象很大时,更会影响内存占用。
懒汉式
public class Lazy {
private static Lazy lazy;
private Lazy() {
}
public static Lazy instance() {
if (lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}
可以在instance()时再创建对象,所以称为懒汉式。这种实现最大的问题就是不支持多线程,存在线程安全问题,所以解决此问题产生了双重判断加锁机制。
双重判断加锁机制–懒汉式
public class Lazy {
private static Lazy lazy;
private Lazy() {
}
public static Lazy instance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
}
这种方式,通过加锁貌似可以解决线程安全问题,并且有了双重判断,第一个if判断加快了执行效率,因为如果判断已经创建了实例,就不需要再进行加锁,但是,仔细思考,发现还是存在问题,原因就在于,对象的实例化过程不是一个原子操作,对于lazy = new Lazy();这步操作,在虚拟机里面是分为两步执行的,分别是实例化对象、给lazy变量赋地址值,这两步操作由于不是原子操作,并且java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制,所以可能存在的情况是两个线程,A线程先执行到这一步,先完成了对象的实例化,还没有完成给变量赋值的时候,此时A线程退出了,然后B线程拿到锁后,因为已经实例化了,但是又未给变量赋值,所以继续进入if块进行初始化赋值,最后退出,这时就会造成堆内存中出现两个Lazy的实例。
这个问题的产生根本就是数据的变化不可见,所以可以采用volatile避免。
public class Lazy {
private static volatile Lazy lazy;
private Lazy() {
}
public static Lazy instance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
}
但是,这样的代码过于臃肿,不便于维护,可读性也差,所以由此产生了另一种更好的方式。
懒加载
public class LazyLoad {
private static class LazyHolder {
private final static LazyLoad lazyLoad = new LazyLoad();
}
private LazyLoad() {
}
public static LazyLoad instance() {
return LazyHolder.lazyLoad;
}
}
这种方式最为优雅,并且可以避免线程安全问题,定义私有内部类,在类加载的时候不会加载私有内部类,只有在调用instance方法的时候才会初始化。这种延迟加载模式,也叫类初始化模式和延迟占位模式。
这种方式在其他地方也应用的非常广,比如:
当一个对象内部的属性是一个占用内存很大,但又不经常使用的对象,如果不使用的时候直接创建会浪费内存空间,所以这时候可以采用这种方式:
public class LazyLoadExmp {
private String name;
//这是一个非常大的数组,不经常使用
private Integer[] integers;
//这种就可以采用延迟加载模式
private static class LazyHolder {
private final static Integer[] integers = new Integer[100000000];
}
public Integer[] getIntegers() {
return LazyHolder.integers;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}