定义:确保某一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例。
也就是 对象不能通过new 来实例化,而是有类本身来实例化一个自己的对象 ,并且创建一个对于这个对象的get方法。
饿汉单例
将类的对象作为类的成员变量,在类声明的时候就已经实例化。
provate static final objct o = new object();
public object getInstance(){
return o;
}
懒汉单例
//在用户第一次调用getInstance的时候进行初始化
public object getInstance(){
if ( o == null ) {
o = new object();
}
return o;
}
Double Check Look (DCL) 单例实现模式
public final class Obj{
private volatile Obj obj ;
public Obj getInstance(){
if(obj == null){
synchronized(Obj.class){
if(obj == null){
obj = new Obj();
}
}
}
return obj;
}
}
单例模式 到了最后,各种各样的单例 最终都是在解决 高并发下 线程安全的问题,和实现真正的单例
问题1:线程安全问题, 例如在复杂高并发情况下,get方法在同一时间或者相隔很短时间内调用了两次。那么就会造成 出现两个实例的情况
一般 线程安全的解决办法就是 使用volatile关键字来修饰变量,用synchronized来修饰方法体或一个方法块;
例如:
public class SingleObject{
private static volatile SingleObject so = null;
public static SingleObject getInstance(){
if(so == null ){
synchronized(SingleObject.class){
if(so == null ){
so = new SingleObject();
}
}
}
}
}
可以看到在成员变量是用volatile修饰,可以保证对象每次都是从主内存中读取。
volatile 或多或少的会影响到性能
而get方法中有两层 判断: 第一层是因为 避免直接的不必要的同步, 第二次则是在同步锁内的判断,这一次则是为了创建实例。
这样写的好处就是 在对象实例化过后,调用get方法 将不会再进行同步锁!
问题2: 序列化, 在java中通过序列化将一个单例 对象 写到磁盘中,然后再读回来,从而有效的获得一个实例。 即使构造方法是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用了该类的构造函数。
解决办法 :
一 、使用枚举单例,没错就是枚举
public enum SingleEnumObject {
INSTANCE;
public void doSomething(){
System.out.println(" class = " +this);
}
}
枚举在java中与普通的类是一样的,不仅能够有字段,还能够有自己的方法,最重要的是默认枚举单例的创建时线程安全的,并且任何情况下它都是一个单例。
注意:
单例模式一般没有接口,所有扩展起来很难,除非修改代码。
单例对象如果持有Context对象,很容易造成 内存溢出, 此时需要注意传递给单例对象的Context最好是 Application Context