我们在JAVA开发过程中经常会用到线程池、缓存、注册表对象、日志对象等对象。这些对象往往只能有一个实例,如果出现多个上述对象的实例,可能会导致很多问题。
当然我们可以定义全局变量,全局变量会在程序一开始就创建好对象,如果该对象在程序执行过程中对资源消耗非常大而且使用率不高,在面对对性能要求高的系统中就成为了累赘。所以我们才需要用到单件模式进行对象创建。
public class SingleClass{
//创建一个静态私有的SingleClass实例成为唯一实例
private static SingleClass Instance;
//私有化SingleClass类构造器
private SingleClss(){}
//采用getInstance方法实例化对象并返回
public static SingleClass getInstance(){
//判断实例是否为空保证只有一个实例产生
if(Instance == null){
Instance = new SingleClass();
}
return Instance;
}
}
这样我们就完成了一个简单的单件模式实现。
多线程编程是java开发中经常使用到的。在多线程环境下,上述的单件模型可能就会出现严重的问题。这是因为JVM中多线程执行顺序导致的。如下:
线程一 | 线程二 | Instance |
---|---|---|
public static SingleClass getInstance(){ | null | |
public static SingleClass getInstance(){ | null | |
if(Instance == null){ | null | |
if(Instance == null){ | null | |
Instance = new SingleClass(); return Instance; | object1 | |
Instance = new SingleClass(); return Instance; | object2 |
多线程模式下我们的单件实例产生了两个对象,有违我们单件模式的初衷。我们可以靠以下方式解决多线程下的单件多对象问题:
(1)将getInstance()加同步关键字synchronized
public static synchronized SingleClass getInstance(){
if(Instance == null){
Instance = new SingleClass();
}
}
如果该程序调用getInstance()非常频繁,则同步方法会造成较大的运行负担,降低运行效率。所以需要考虑别的方法
(2)不再延迟实例化
public class SingleClass{
private static SingleClass Instance = new SingleClass();
private SingleClass();
public static SingleClass getInstance(){
return Instance;
}
}
这样在线程一的一开始就为静态变量Instance创建了SingleClass实例,下面的getInstance()方法直接返回此实例,多线程中就不会产生出现两个对象实例的情况。
(3)volatile 关键字
使用volatile关键字 当Instance 被初始化的时候,多线程可以正确处理Instance变量。
volatile 的特性
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
- 禁止进行指令重排序。(实现有序性)
- volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
public class SingleClass{
private volatile static SingleClass Instance;
private SingleClass(){}
//volatile 检查不存在实例后才进入下方同步区块
public static SingleClass getInstance(){
//只有第一次创建实例执行以下代码
if(Instance == null){
synchronized (SingleClass.class){
if(Instance == null){
Instance = new SingleClass();
}
}
}
return Instance;
}
这种做法可以优化性能,减少频繁synchronized的时间开销。