定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
类图
类型
饿汉式
线程安全,调用效率高,但是不能延迟加载。
public class HungrySingleton
{private static final HungrySingleton instance=new HungrySingleton();private HungrySingleton(){}public static HungrySingleton getInstance(){return instance;}
}
懒汉式
线程安全,调用效率不高,并且可以延迟加载。
public class LazySingleton
{private static volatile LazySingleton instance=null; //保证 instance 在所有线程中同步private LazySingleton(){} //private 避免类在外部被实例化public static synchronized LazySingleton getInstance(){//getInstance 方法前加同步if(instance==null){instance=new LazySingleton();}return instance;}
}
双重检查锁式
双重检查锁模式解决了单例、性能、线程安全问题。但在多线程情况下,可能会出现空指针问题。问题在于JVM 在实例化对象时会进行优化和指令重排序操作。解决空指针问题只需使用volatile 关键字,volatile 可以保证可见性和有序性。
public class LazyMan3 {private LazyMan3(){}private static volatile LazyMan3 instance;public static LazyMan3 getInstance(){//第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if (instance == null){synchronized (LazyMan3.class){//第二次判断if (instance == null){instance = new LazyMan3();}}}return instance;}
}
class LazyMan3Test{public static void main(String[] args) {LazyMan3 instance = LazyMan3.getInstance();LazyMan3 instance1 = LazyMan3.getInstance();System.out.println(instance == instance1);}
}
静态内部类式
线程安全,调用效率不高,可以延迟加载;
public class LazyMan4 {private LazyMan4(){}//定义一个静态内部类private static class LazyMan4Holder{private static final LazyMan4 INSYANCE = new LazyMan4();}//对外访问方法public static LazyMan4 getInstance(){return LazyMan4Holder.INSYANCE;}
}
class LazyMan4Test{public static void main(String[] args) {LazyMan4 instance = LazyMan4.getInstance();LazyMan4 instance1 = LazyMan4.getInstance();System.out.println(instance == instance1);}
}
静态代码块式
public class HungryChinese2 {//私有构造方法,为了不让外界创建该类的对象private HungryChinese2(){}//声明该类类型的变量private static HungryChinese2 hungryChinese2;//初始值为null//静态代码块中赋值static {hungryChinese2 = new HungryChinese2();}//对外提供的访问方式public static HungryChinese2 getInstance(){return hungryChinese2;}
}
class HungryChinese2Test{public static void main(String[] args) {HungryChinese2 instance = HungryChinese2.getInstance();HungryChinese2 instance1 = HungryChinese2.getInstance();System.out.println(instance.equals(instance1));}
}
枚举式
线程安全,调用效率高,不能延迟加载,但是可以天然的防止反射和反序列化漏洞。
public enum LazyMan5 {INSTANCE;
}
class LazyMan5Test{public static void main(String[] args) {LazyMan5 instance = LazyMan5.INSTANCE;LazyMan5 instance1 = LazyMan5.INSTANCE;System.out.println(instance == instance1);}
}
应用场景
-
某个类只要求生成一个对象的时候。
-
当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如Web中的配置对象、数据库的连接池等。
-
当某类需要频繁实例化,而创建的对象又频繁被销毁时,如多线程的线程池、网络连接池等。
单例问题
Serializable
- 问题:如果单例类实现了java.io.Serializable 接口,那么这个类可能会被反序列化,并且反序列化多次使用同一对象时,会得到多个单例类的实例,这样就不是单例了;
- 解决方法:需要添加如下方法
// 反序列化时,如果定义了readResolve() 则直接返回此方法指定的对象,而不需要单独再创建新对象了;
private Object readResovle() throws ObjectStreamException{// TODO Auto-generated method stubreturn instance;
}
在Android 中使用单例模式可能会内存泄漏
- 问题:当调用getInstance 时,如果传入的context是Activity 的context 。只要这个单例没有被释放,那么这个Activity 也不会被释放,一直到进程退出才会释放。
public class CommUtils {private volatile static CommUtils mCommUtils;private Context mContext;public CommUtils(Context context) {mContext=context;}public static CommUtils getInstance(Context context) {if (mCommUtils == null) {synchronized (CommUtils.class) {if (mCommUtils == null) {mCommUtils = new CommUtils(context);}}}return mCommUtils;}
}
- 解决方法:能使用Application的Context就不要使用Activity的Context,Application的生命周期伴随着整个进程的周期。