------引入
我们在使用Windows的时候无法同时打开两个任务管理器,也就是说,它在整个系统中只有一个唯一的一个实例。怎样实现在一个系统中某个类的实例只能唯一存在呢?单例模式就是很好的解决方法。
单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色只有一个,那就是单例类——Singleton。
一.饿汉模式
在这个类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。Java语言中单例类的一个重要的特点是类的构造函数是私有的,从而避免外界利用构造函数直接创建出任意多的实例。
//1.为什么加final?
//回答:避免EagerSingleton的子类重写父类方法,破坏单例模式。
public final class EagerSingleton {
private static EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return instance;
}
}
二.懒汉模式
与饿汉模式相同之处是,懒汉模式单例类的构造函数也是私有的,与饿汉模式单例类不同的是,饿汉模式单例类在第一次被引用时将自己实例化,在懒汉模式单例类被加载时不会将自己实例化。
例1:单线程版
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
问题:
例如两个线程同时进入如下代码块,那么会new出两个不同的实例,那就违背了对象只有一个的目的。
例2:多线程版——性能低
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
问题:
public synchronized static Singleton getInstance()保证了线程安全性;
但是只有在第一次执行此方法时,才真正需要同步,
所以一旦初始化完毕后,就每次调用这个方法,同步都是多余的。增加了开销。
例3:多线程版——二次判断——性能高
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
注意:
首先会检查实例是否创建,如果没有,才进行同步,保证只有在第一次时会同步。避免了开销。
volatile关键字的作用:修饰的共享变量,可以保证可见性,部分保证顺序性。
实例化对象instance = new Singleton();
可以分成三个步骤:
1.分配内存空间
2.初始化对象
3.将对象指向刚分配的内存空间
但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
1.分配内存空间
2.将对象指向刚分配的内存空间
3.初始化对象
如果顺序1->2->3顺序变为,1->3->2.此时1->3时,对象初始化并未完成,
但是此时另一个线程又判断instance == null,又开始了对象的初始化工作。
使用volatile关键字可以解决在这个问题。
详细再分析饿汉模式:
//1.为什么加final?
//回答:避免EagerSingleton的子类重写父类方法,破坏单例模式。
//2.如果实现了序列化接口,还要做什么来防止反序列化破坏单例?
//回答:反序列化会生成新对象,新生成的对象与之前单例模式生成的对象相比就是不同的对象,这样明显破坏了单例模式。增加方法在下面.
public final class EagerSingleton implements Serializable{
//问题:为什么加上static?
//静态成员变量的创建在类初始化过程中创建,由JVM保证线程安全
private static final EagerSingleton instance = new EagerSingleton();
//3.为什么设置为私有?是否能够防止反射创建新的实例?
//回答:非私有状态下,其它类也可以创建对象,就不能保证单例。
//回答:不能。反射可以得到构造器对象。它可以设置setSerializable属性为true。然后调用构造方法创建新的实例。
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return instance;
}
//作用:在反序列化过程中,一旦发现readResovle()方法返回了instance对象,就不会采用反序列化时字节码生成的对象,它将这个方法返回的对象当做反序列化的结果。
public Object readResovle(){
return instance;
}
}