单例实现基础
在类加载的时候,静态属性或静态构造块进行初始化,由于类只被加载器加载一次,所有静态属性或静态构造块初始化一次
1、饥汉单例模式
单例对象在加载类的时候就创建对象并初始化,在方法区中的静态数据区开辟一个静态该类引用类型,该类型指向堆中对象。并提供一个静态方法返回一个该静态类引用
public class Singleton {
private static Singleton instance = new Singleton(5);
private static Singleton instance;
/*也可以提供静态初始化快
static{
Singleton instance = new Singleton(5);
}*/
private int count;
/*将构造方法私有化,不让在类外用new调用构造方法创建对象并初始化对象
*必须用getInstance方法得到该对象
*/
private Singleton(int count) {
this.count = count;
}
public static Singleton getInstance() {
return instance.instance.instance…;
}
}
注:其实该单例在内存中存在一个环的结构
instance.instance表示堆中先找到堆中的instance的引用(所指向的方法区中的instance)
然后再返回堆中对象的地址,然后再找到instance的引用,无论有多少个instance.instance
始终返回的就是当前堆中的对象这样就形成了环状
2、饱汉模式(懒汉模式)
首先当调用getInstance()方法时候才创建对象,否则不会创建
//提供静态全局变量
private static Singleton instace = null;
/*将构造方法私有化,不让在类外用new调用构造方法创建对象并初始化对象
*必须用getInstance方法得到该对象
*/
private Singleton(){
}
public static Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}
3、类加载器创建单例(对象在内部类SingletonHolder中创建的)
当调用getInstance方法时会加载SingletonHolder类并在方法区初始化INSTANCE,
因为类加载器只加载一次类SingletonHolder,并在堆中创建对象,当需要时才会创建该对象,加载Singleton类时不会创建对象, 是一种懒加载模式
public class Singleton {
private static class SingletonHolder {
static final Singleton INSTANCE = new Singleton();
}
/*将构造方法私有化,不让在类外用new调用构造方法创建对象并初始化对象
*必须用getInstance方法得到该对象
*/
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
4、Map集合实现单例
5、枚举实现单例
较比以上实现单例的方法优势:
1.线程安全
2.不能通过反射创建对象
3.不能通过反序列化创建对象
/*使用枚举实现单例模式
* 好处:不可以通过反射创建对象,不可反序列化(不可以实现Serizalable),线程安全
*
*/
enum Singleton {
INSTANCE {
private int age;
@Override
public int getAge(){
return age;
}
@Override
public void setAge(int age) {
// TODO Auto-generated method stub
this.age=age;
}
};
public abstract int getAge();
public abstract void setAge(int i);
}
6、单例的扩展
其中以上汉模式和(饱汉模式)懒汉模式在多线程的情况下可能会出现多个对象,其中只有类加载的单例模式,jvm保证了线程的安全性,因为类加载器一次只能加载一个类,同时SingletonHolder类是private只有getInstance才能访问
能在多线程下使用的单例
一、在geInstance()方法前加synchronized
但是多了一个synchroized耗时比返回一个实例还长
二、在方法内加synchronized
能在多线程下使用的单例
一、在geInstance()方法前加synchronized
但是多了一个synchroized耗时比返回一个实例还长
二、在方法内加synchronized
public staic Singleton getInstance() {
synchronized(Singleton.class) {
if(instance == null)
instance = new Singleton();
}
return instance;
这每次调用getInstance()都需要同步,性能也会降低这和(一)的性能一样低
三、双重锁定检查DCL,首先判断instance为null 才进行同步
public staic Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null)
instance = new Singleton();
}
}
return instance;
这里看起来是一句话,但实际上它并不是一个原子操作(原子操作的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形)。事实上高级语言里面非原子操作有很多,我们只要看看这句话被编译后在JVM执行的对应汇编代码就发现,这句话被编译成8条汇编指令,大致做了3件事情:
1.给Kerrigan的实例分配内存。
2.初始化Kerrigan的构造器
3.将instance变量指向分配的内存空间(注意到这步instance就非null了)。
但是,由于Java编译器允许处理器乱序执行(out-of-order),以及JDK1.5之前JMM(Java Memory Medel)中Cache、寄存器到主内存回写顺序的规定,上面的第二点和第三点的顺序是无法保证的,也就是说,执行顺序可能是1-2-3也可能是1-3-2,如果是后者,并且在3执行完毕、2未执行之前,被切换到线程二上,这时候instance因为已经在线程一内执行过了第三点,instance已经是非空了,所以线程二直接拿走instance,然后使用,然后顺理成章地报错,而且这种难以跟踪难以重现的错误估计调试上一星期都未必能找得出来,真是一茶几的杯具啊。
取决于是否能保证2、3步的顺序。在JDK1.5之后,官方已经注意到这种问题,因此调整了JMM、具体化了volatile关键字,因此如果JDK是1.5或之后的版本,只需要将instance的定义改成“private volatile static SingletonKerriganD instance = null;”就可以保证每次都从主内存读取,就可以使用DCL的写法来完成单例模式。当然volatile或多或少也会影响到性能
private volatile static Singleton instance
public staic Singleton getInstance() {
synchronized(Singleton.class) {
if(instance == null)
instance = new Singleton();
}
return instance;
创建单例对象的方法
通过new创建单例对象
通过反射构造单例对象
通过序列化构造单例对象
序列化方式:该类实现Serializable接口
public class Singleton implemets Serializable {
private static class SingletonHolder {
static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static getInstance() {
return SingletonHolder.INSTANCE;
}
//此方法返回一个反序列后的单例对象,如果不重写的话,得到就不是单例对象 每次序列化出来的对象都是一个新的对象
private Object readResolve() {
return getInstrance();
}