单例模式作为常用的设计模式之一,创建单一对象并在程序活动中提供唯一实例。一般而言,单例模式的要求有
- 单例类只能有一个实例
- 单例类必须自己创建唯一的实例
- 单例类必须提供获取唯一实例的方法
项目 | 说明 |
目的 | 提高内存使用效率,在程序中提供全局唯一实例 |
需求 | 控制实例数目,节省系统资源的时候 |
思路 | 一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。 |
关键 | 构造函数的私有化 |
应用实例 | 设备管理器如打印机等;多进程操作同一文件 |
优点 | 减少内存开销,符合 |
缺点 | 违背单一职能原则,无法继承 |
使用场景 | web中的计数器,多线程处理单一文件 |
单例模式在单线程时不会出现问题,但在多线程时会由于计算机多线程处理方式的问题产生错误。由此JAVA推荐双锁机制,在获取实例方法中加入锁,并且确保该实例的修改会立刻反应在主存中。
在参考和修改现有实现模式的基础上,构建如下实现。
双锁(Double-Check)机制
package singleton_pattern;
public class SingleObject {
private volatile static SingleObject instance;
private final static Object lock=new Object();
//当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。<可见性>
//内部静态声明一个变量锁
private SingleObject() {
}
//把构造函数写成私有就不能外部调用了
public static SingleObject getInstance() {
if (instance==null)
{
synchronized (lock) {
//synchronized和lock能够保证任一时刻只有一个线程执行该代码块<原子性>
//将getInstance方法标记为synchronized是对类实例进行同步
//synchronized(类定义)是对类同步
//synchronized(object)减小同步的粒度
//在这里加同步锁要比将getInstance方法标记为synchronized效率要高
if (instance==null) {
instance=new SingleObject();
}
}
}
return instance;
}
//通过该方法获取单一的实例
public void ShowMessage() {
System.out.println("Hello world!");
}
}
其中涉及到几个关键词,volatile和synchronized,关于为什么要这样写,可以参照:
- Java并发编程:volatile关键字解析
- Java synchronized详解
在wiki上推荐的是“饿汉式”的实现方式,即在程序开始前就初始化该类的实例。
public class Something {
private Something() {}
private static class LazyHolder {
private static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
优点是实现方式简单,而且并发时不会导致错误,缺点是生成了无用对象,不满足 lazy loading。
本文是学习之余的记录,只做分享交流使用。恳请批评指正!