Java多线程(6)
七种单例设计模式的设计
饿汉式
public class Singleton {
// 实例变量
private byte[] data = new byte[1024];
// 定义实例对象的时候直接初始化
private static Singleton instance = new Singleton();
// 私有构造函数
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
饿汉式的关键在于instance作为类变量并且直接得到了初始化,如果主动使用Singleton类,那么instance实例将会直接完成创建
insta作为类变量在类初始化的过程中会被收集劲< clinit >()方法中,该方法能够百分之百保证同步,也就是instance在多线程的情况下不可能被实例化两次
懒汉式
public final class Singleton {
private byte[] data = new byte[1024];
// 定义实例,但是不直接初始化
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(null == instance){
instance = new Singleton();
}
return instance;
}
}
懒汉式就是使用类实例的时候再去创建
Singleton.class被初始化的时候instance并不会被实例化,但是getInstance方法放在多线程环境下分析,则会导致instance被实例化不止一次,并不能保证单例的唯一性
懒汉式 + 同步方法
public class Singleton {
private byte[] data = new byte[1024];
private static Singleton instance = null;
private Singleton() {
}
// 加入同步控制,每次只有一个线程能够进入
public synchronized static Singleton getInstance() {
if (null==instance){
instance = new Singleton();
}
return instance;
}
}
考虑到懒汉式无法保证实例的唯一性,此处使用synchronize关键字来修饰getInstance方法,来保证数据的同步性,但是这种方法性能低下。
Double - Check
public class Singleton {
private byte[] data = new byte[1024];
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 当instance为null,进入同步代码块,避免了每次都要进入
if (null == instance){
// 只有一个线程能够获得Singleton.class关联的锁
synchronized (Singleton.class){
if (null == instance){
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查机制首次初始化的时候加锁,之后则允许多个线程同时进行getInstance调用获取类的实例
当两个线程发现null == instance时候,只有一个线程有资格进入同步代码块,完成instance的实例化,随后线程发现null == instance不成立时无需进行任何动作
Volatile + Double - Check
上述Double - Check写法看似解决了问题,但是有个很大的隐患。实例化对象的那行代码(instance = new Singleton();),实际上可以分解成以下三个步骤:
- 分配内存空间
- 初始化对象
- 将对象指向刚分配的内存空间
但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
-
分配内存空间
-
将对象指向刚分配的内存空间
- 初始化对象
这种情况下对应到就是singleton已经不是null,而是指向了堆上的一个对象,但是该对象却还没有完成初始化动作。当后续的线程发现singleton不是null而直接使用的时候,就会出现意料之外的问题
可以使用volatile关键字修饰变量来解决无序写入产生的问题,因为volatile关键字的一个重要作用是禁止指令重排序,即保证不会出现内存分配、返回对象引用、初始化这样的顺序,从而使得双重检测真正发挥作用
public class Singleton {
private byte[] data = new byte[1024];
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 当instance为null,进入同步代码块,避免了每次都要进入
if (null == instance){
// 只有一个线程能够获得Singleton.class关联的锁
synchronized (Singleton.class){
if (null == instance){
instance = new Singleton();
}
}
}
return instance;
}
}
Holder方式
public class Singleton {
private byte[] data = new byte[1024];
private Singleton() {
}
// 在静态内部类中持有Singleton实例,并且直接进行初始化
private static class Holder{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return Holder.instance;
}
}
Holder方式完全借助了类加载的特点
此方式又可以保证数据的同步性,也能保证内存的可见性,指令顺序性,原子性
枚举方式
public enum Singleton {
INSTANCE;
private byte[] data = new byte[1024];
Singleton()
{
System.out.println("INSTANCE will be 初始化");
}
public static void method(){
// 调用该方法会主动使用Singleton,INSTANCE将被实例化
}
public static Singleton getInstance(){
return INSTANCE;
}
}
枚举类型不允许继承,同样是线程安全的且只能被实例化一次
网友评论