确保某一个类只有一个实例,
而且自行实例化并向整个系统提供这个实例。
确保某个类有且只有一个对象的场景,
避免产生多个对象消耗过多的资源,
或者
某种类型的对象只应该有且只有一个。
例如,
创建一个对象需要消耗的资源过多,
如要访问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对象**返回,
而不是默认的重新生成一个新的对象。
而对于枚举,并不存在这个问题,
因为即使**反序列化**它也不会**重新生成**新的实例。
参考: