确保某一个类
只有一个实例
,
而且自行实例化
并向整个系统
提供这个实例
。
确保某个类有且只有一个对象的场景,
避免产生多个对象消耗过多的资源,
或者
某种类型的对象只应该有且只有一个。
例如,
创建一个对象需要消耗的资源过多,
如要访问IO和数据库等资源,这时就要考虑使用单例模式。
(1)Client——高层客户端;
(2)Singleton——单例类。
构造函数
**不对外开放,一般为**Private
**;
(2)通过一个**静态方法
**或者**枚举
**返回**单例类对象
**;
(3)确保**单例类的对象
**有且只有一个,尤其是在**多线程环境
**下;
(4)确保**单例类对象
**在**反序列化时
**不会重新**构建对象
**。构造函数私有化
**,
使得客户端代码不能通过 new
的形式手动构造**单例类的对象
**。单例类
**会暴露一个**公有静态方法
**,**
客户端
**需要调用这个**静态方法
**获取到**单例类
**的**唯一对象
**;**线程安全
**,
即在**多线程环境
**下构造**单例类的对象
**也是**有且只有一个
**,
这也是**实现的难点
**。声明一个**静态类对象
**,在**声明时
**就己经**初始化
**,
用户调用**类对象get方法
**时,可以直接拿去用;
【一声明就初始化,所谓“饿”】
如下,
CEO类使用了饿汉单例模式;
/**
* 普通员工
*/
class Staff {
public void work() {
// 干活
}
}
// 副总裁
class VP extends Staff {
@Override
public void work() {
// 管理下面的经理
}
}
// CEO, 饿汉单例模式
class CEO extends Staff {
private static final CEO mCeo = new CEO();
// 构造函数私有
private CEO() {
}
// 公有的静态函数,对外暴露获取单例对象的接口
public static CEO getCeo() {
return mCeo;
}
@Override
public void work() {
// 管理VP
}
}
// 公司类
class Company {
private List<Staff> allPersons = new ArrayList<Staff>();
public void addStaff(Staff per) {
allPersons.add(per);
}
public void showAllStaffs() {
for (Staff per : allPersons) {
System.out.println("Obj : " + per.toString());
}
}
}
public class Test {
public static void main(String[] args) {
Company cp = new Company();
// CEO对象只能通过getCeo函数获取
Staff ceo1 = CEO.getCeo();
Staff ceo2 = CEO.getCeo();
cp.addStaff(ceo1);
cp.addStaff(ceo2);
// 通过new创建VP对象
Staff vp1 = new VP();
Staff vp2 = new VP();
// 通过new创建Staff对象
Staff staff1 = new Staff();
Staff staff2 = new Staff();
Staff staff3 = new Staff();
cp.addStaff(vp1);
cp.addStaff(vp2);
cp.addStaff(staff1);
cp.addStaff(staff2);
cp.addStaff(staff3);
cp.showAllStaffs();
}
}
第一次
**调用**getInstance
**时**才
**进行**初始化
**;
【“拖延”,等到调用才初始化,所谓“懒”!】 public class Singleton {
private volatile static Singleton instance;
private Singleton () {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton ();
}
return instance;
}
}
getInstance()
**中添加了** synchronized
关键字,
也就是 getInstance()
**是一个同步方法,**
即上面所说的在**多线程
**情况下保证**单例对象唯一性
**的手段。instance
**己经被初始化(第一次调用时就会被初始化instance),
每次
**调用**getInstance
**方法都会进行**同步
**,**
这样会消耗不必要的资源,这也是懒汉单例模式存在的最大问题。节约了资源
**;同步
**,造成不必要的**同步开销
**。这种模式一般不建议使用!!!!!!!!!!
getInstance()
**时单例对象才会被实例化,效率高。
既能够在**需要时才初始化
**单例,
又能够保证**线程安全
**,
且单例对象初始化后每次调用**getInstance()
**不进行**同步锁
**,
减少不必要的**同步开销
**:public class Singleton {
private volatile static Singleton sInstance = null;
private Singleton() {
}
public void doSomething() {
System.out.println("do sth.");
}
public static Singleton getInstance() {
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
getInstance()
**方法中对**instance
**进行了两次判空:
第一层判断主要是为了避免不必要的同步【有实例则直接返回,没必要同步】,
第二层的判断则是为了在**null的情况
**下**创建实例
**
【可能第一层与第二层判断中途有其他线程初始化完成了单例,
单例不为**null
**,就不用创建了】: 假设线程A和线程B先后访问了**getInstance()
**;
线程A执行到**sInstance = new Singleton()
**语句,
这里看起来是一句代码,但实际上它并不是一个**原子操作
**,
这句代码最终会被编译成多条汇编指令,它大致做了**3件
**事情:
(1)给Singleton的实例**分配内存
**;
(2)调用**Singleton()
**的**构造函数
**,**初始化
**成员字段;
(3)将**sInstance对象
**指向分配的内存空间(此时sInstance就不是**null
**了)。
但是,由于Java编译器允许处理器**乱序执行
**,
以及JDK1.5之前**JMM(Java Memory Model,即Java内存模型)
**中Cache、
寄存器到主内存回写顺序的规定,
上面的第二和第三的顺序是**无法保证
**的。【指令重排序】
即,执行顺序可能是1-2-3也可能是1-3-2。
如果是后者,并且在**3执行完毕
**、**2未执行之前
**,被切换到**线程B
**上,
这时候**sInstance
**因为己经在线程A内执行过了第三点,
sInstance
**己经是**非空
**了,**
所以,
线程B
**通过**getInstance()
**直接取走**sInstance
**,**
再使用时就会**出错
**,这就是**DCL失效问题
**,
而且这种难以跟踪难以重现的错误很可能会隐藏很久。
在JDK1.5之后,SUN官方己经注意到这种问题,
调整了JVM,具体化了**volatile
**关键字,
因此,
如果JDK是1.5或之后的版本,
只需要将**sInstance
**的定义改成**private volatile static Singleton sInstance = null
**就可以保证sInstance对象**每次都是从主内存中读取
**,
就可以使用DCL的写法来完成**单例模式
**。
当然,**volatile
** 或多或少也会影响到**性能
**,
但考虑到程序的**正确性
**,牺牲这点性能还是值得的。^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
使用最多
**的单例实现方式!!!!
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
它能够在**需要时才
**实例化单例对象,
并且能够在绝大多数场景下保证单例对象的**唯一性
**,
除非代码在并发场景比较复杂或者低于JDK 6版本下使用,
否则,这种方式一般能够满足需求。资源消耗、多余的同步、线程安全
**等问题,
但是,它还是在某些情况下出现失效的问题。
就是刚说的**双重检查锁定(DCL)失效
**;丑陋
**的,**不赞成使用
**。
而建议使用如下的代码**替代
**:public class Singleton {
private Singleton() { }
public static Singleton getInstance () {
return SingletonHolder.sInstance;
}
/**
* 静态内部类
*/
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
Singleton类
**时并不会初始化**sInstance
**,
只有在第一次调用**Singleton
**的**getInstance()
**时**sInstance
**才会被初始化!!!
因此,
第一次调用**getInstance()
**会导致虚拟机加载**SingletonHolder类
**,
这种方式不仅能够确保**线程安全
**,
也能够保证单例对象的**唯一性
**,同时也**延迟
**了单例的**实例化
**,
所以这是推荐使用的单例模式实现方式。除了以上几种方式,还有更简单的实现方式——枚举!:
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("do sth.");
}
}
枚举
**在Java中与普通的类是一样的,**
不仅能够有字段,还能够有自己的方法。
最重要的是**默认枚举实例
**的**创建
**是**线程安全
**的,
并且在**任何情况下
**它都是一个**单例
**。
在上述的几种单例模式实现中,
在一个情况下它们会出现**重新创建对象
**的情况,那就是**反序列化
**。
通过**序列化
**可以将**一个单例
**的**实例对象
**写到**磁盘
**,
然后再**读回来
**,从而有效地**获得一个实例
**。
即使**构造函数
**是**私有
**的,
反序列化
**时依然可以通过特殊的途径去创建类的一个**新的实例
**,**
相当于调用该类的**构造函数
**。
反序列化
**操作提供了一个很特别的**钩子函数
**,**
类中具有一个**私有的、被实例化
**的方法**readResolve()
**,
这个方法可以让开发人员控制对象的**反序列化
**。
例如,
上述几个示例中如果要杜绝单例对象在被反序列化时重新生成对象,
那么必须加入如下方法:
private Object readResolve() throws ObjectStreamException {
return sInstance;
}
即在**readResolve()
**中将**sInstance对象
**返回,
而不是默认的重新生成一个新的对象。
而对于枚举,并不存在这个问题,
因为即使**反序列化
**它也不会**重新生成
**新的实例。
参考: