定义:确保一个类只有一个实例,并且自行实例化并像整个系统提供该实例
特点:
1.私有的构造方法
2. 指向自己唯一实例的静态引用引用
3. 自己创建自己的唯一实例,并向外部提供该唯一实例
单例模式 常用的有懒汉模式 ,饿汉模式,静态内部类,枚举 饿汉模式在类被加载的时候就被实例化,而懒汉模式 是在使用的时候才被实例化
饿汉模式:(牺牲空间换取时间)
public class SingelDemo {
private static SingelDemo single = new SingelDemo();
private SingelDemo(){};
public static SingelDemo newInstance(){
return single;
}
}
饿汉模式,是一种形象的称谓,既然饿,那么就会迫不及待的创建对象,在类装在的时候就创建了对象。
懒汉模式-同步方法:(始终用不到的单例无需加载)
public class SingleDemo1 {
private static SingleDemo1 singleDemo1;
private SingleDemo1(){}
public static synchronized SingleDemo1 newInstance(){
if(singleDemo1 == null){
singleDemo1 = new SingleDemo1();
}
return singleDemo1;
}
}
添加synchronized方法之后会影响性能,因此引入
懒汉模式-双重检查机制(注意需要volatile修饰)
public class SingleDemo1 {
private static volatile SingleDemo1 singleDemo1;
private SingleDemo1(){}
public static SingleDemo1 newInstance(){
if(singleDemo1 != null){
return singleDemo1;
}
synchronized (SingleDemo1.class){
if (singleDemo1 == null){
singleDemo1 = new SingleDemo1();
}
}
return singleDemo1;
}
}
new 实例背后的指令
这个被忽略的问题在于 singleDemo1 = new SingleDemo1();这行代码并不是一个原子指令。使用 javap -c指令,可以快速查看字节码。
从字节码可以看到创建一个对象实例,可以分为三步:
分配对象内存
调用构造器方法,执行初始化
将对象引用赋值给变量。
虚拟机实际运行时,以上指令可能发生重排序。以上代码 2,3 可能发生重排序,但是并不会重排序 1 的顺序。也就是说 1 这个指令都需要先执行,因为 2,3 指令需要依托 1 指令执行结果。
Java 语言规规定了线程执行程序时需要遵守 intra-thread semantics。**intra-thread semantics ** 保证重排序不会改变单线程内的程序执行结果。这个重排序在没有改变单线程程序的执行结果的前提下,可以提高程序的执行性能。
虽然重排序并不影响单线程内的执行结果,但是在多线程的环境就带来一些问题。
上面错误双重检查锁定的示例代码中,如果线程 1 获取到锁进入创建对象实例,这个时候发生了指令重排序。当线程1 执行到 t3 时刻,线程 2 刚好进入,由于此时对象已经不为 Null,所以线程 2 可以自由访问该对象。然后该对象还未初始化,所以线程 2 访问时将会发生异常。
volatile 作用
正确的双重检查锁定模式需要需要使用 volatile。volatile主要包含两个功能。
保证可见性。使用 volatile定义的变量,将会保证对所有线程的可见性。
禁止指令重排序优化。
由于 volatile禁止对象创建时指令之间重排序,所以其他线程不会访问到一个未初始化的对象,从而保证安全性。
注意,volatile禁止指令重排序在 JDK 5 之后才被修复
静态内部类:
public class SingleDemo1 {
private SingleDemo1(){}
private static class InnerClass{
private static final SingleDemo1 singleDemo = new SingleDemo1();
}
public static SingleDemo1 getInstance(){
return InnerClass.singleDemo;
}
}
静态内部类:内部类InnerClass 在外部类SingleDemo1 被类加载器加载时候 没有被加载,当调用 getInstance时候使用到内部类
InnerClass 时候,由类加载器加载(由类加载器确保了单例)实现了单例
枚举:
public enum SingleDemo1 {
INSTANCE;
}
单例模式的优点:
1.只有一个实例节省内存空间
2.避免了频繁 创建和销毁对象
懒汉模式 和饿汉模式对比:
1,饿汉模式 在初始化的时候创建对象,饿汉模式在使用的时候创建对象
2. 饿汉模式在初始化创建对象,天生的是线程安全的,懒汉模式可以采取 同步方法块但是性能不好,因此引入了双重检查机制,提高性能
(线程安全问题可以参考:http://blog.youkuaiyun.com/jason0539/article/details/23297037/)