单例模式instance

单例模式是设计模式中最简单的模式了,它的目的是为了保证一个流程中只有一个对象存在,相当于一个全局变量。

1 单例模式的实现
创建一个类,调用者不能通过默认构造方法的方式创建实例,而是提供一个接口用来返回唯一的实例。

public class SingleInstance{
    private static SingleInstance instance = null;
    private SingleInstance(){}
    public static SingleInstance getInstance(){
        if(instance == null){
            instance = new SingleInstance();
        }
        return instance;
    }
}

2 同步单例模式的实现
1中是最简单的单例模式,但是在多线程状态下就出现问题了,如果线程A调用getInstance()方法时,开始先判断instance是否为空,如果为空则需要创建实例,但是此时A线程阻塞,B线程也调用了getInstance()方法,也发现Instance为空,所以需要创建实例,此时A线程继续执行,则又创建了一个实例,A和B线程各创建了一个实例对象,违背了单例模式的初衷,所以需要实现单例模式的线程同步。

public class SingleInstance{
    private static SingleInstance instance = null;
    private SingleInstance(){}
    public synchronized static SingleInstance getInstance(){
        if(instance == null){
            instance = new SingleInstance();
        }
        return instance;
    }
}

3 提高单例模式的性能
2中的单例模式保证了线程的同步问题,但是也存在着问题,那就是性能问题,采用synchronized关键字来回对方法加锁有很大的性能开销,那么能否只对某个对象加锁呢,答案是可以的。去掉方法同步的关键字synchronized,对SingleInstance.class进行加锁操作,代码可如下:

public class SingleInstance{
    private static SingleInstance instance = null;
    private SingleInstance(){}
    public static SingleInstance getInstance(){
        synchronized(SingleInstance.class){
            if(instance == null){
                instance = new SingleInstance();
            }        
        }
        return instance;    
    }
}

接下来的问题是当多个线程同时获取实例时,依然要对某个对象进行同步操作,有没有更好的办法呢,答案是有的,那就是在同步之前先判断Instance是否为空,如果已经是非空了,也就没必要同步了,那么可以改成如下的代码,如下所示:

public class SingleInstance{
    private static SingleInstance instance = null;
    private SingleInstance(){}
    public static SingleInstance getInstance(){
        if(instance == null){
            synchronized(SingleInstance.class){
                if(instance == null){
                    instance = new SingleInstance();
                }        
            }
        }
        return instance;    
    }
}

看似没有任何问题了,其实不然,主要问题在于instance = new SingleInstance()这行代码并不是原子性的,也就是说,这行代码需要处理器分为多步才能完成,其中主要包含两个操作,分配内存空间,引用变量指向内存,由于编译器可能会产生指令重排序的优化操作,所以两个步骤不能确定实际的先后顺序,假如线程A已经指向了内存,但是并没有分配空间,线程A阻塞,那么当线程B执行时,会发现Instance已经非空了,那么这时返回的Instance变量实际上还没有分配内存,显然是错误的。

4 单例模式中的volatile
volatile是Java提供的关键字,它具有可见性和有序性,被volatile修饰的写变量不能和之前的读写代码调整,读变量不能和之后的读写代码调整,所以,只要把instance加上volatile关键字就可以避免3中的问题了。代码如下所示:

public class SingleInstance{
    private static volatile SingleInstance instance = null;
    private SingleInstance(){}
    public static SingleInstance getInstance(){
        if(instance == null){
            synchronized(SingleInstance.class){
                if(instance == null){
                    instance = new SingleInstance();
                }        
            }
        }
        return instance;    
    }
}

5.enum实现方式
传统的两私有一公开(私有构造方法、私有静态实例(懒实例化/直接实例化)、公开的静态获取方法)涉及线程安全问题(即使有多重检查锁也可以通过反射破坏单例),

目前最为安全的实现单例的方法是通过内部静态enum的方法来实现,因为JVM会保证enum不能被反射并且构造器方法只执行一次。

 package org.mlinge.s10;
 
public class ClassFactory{ 
	
	private enum MyEnumSingleton{
		singletonFactory;
		
		private MySingleton instance;
		
		private MyEnumSingleton(){//枚举类的构造方法在类加载是被实例化
			instance = new MySingleton();
		}
 
		public MySingleton getInstance(){
			return instance;
		}
	} 
 
	public static MySingleton getInstance(){
		return MyEnumSingleton.singletonFactory.getInstance();
	}
}
 
class MySingleton{//需要获实现单例的类,比如数据库连接Connection
	public MySingleton(){} 
}

ClassFactory.getInstance()

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值