对象只要利用自己的属性完成自己的任务,那该对象就是承担责任。除了维持自身的一致性,该对象无需承担其他任何责任。如果该对象还承担其他责任,而其它对象又依赖于该特定对象所承担的责任,就需要得到该特定的对象
单例模式的目的
将类的责任集中到唯一的单体对象中,确保该类只有一个实例,并且为该类提供一个全局访问点
单例模式的难点不在于单例模式的实现,而在于系统中任何识别单例都会保证单例的唯一性
单例模式的实现
1> 提供唯一的私有构造器,避免多个单例对象被创建,这也意味着该单体类不能有子类,因此声明单例类为final是一个好主意,并且让编译器去使用一些性能优化选项。如果有子类的话使用protected,protected的构造方法可以被其子类以及在同一个包中的其它类调用。私有构造器可以防止客户程序员通过除由我们提供的方法之外的任意方式来创建一个实例,如果不把构造器声明为private 或 protected,编译器会自动的创建一个 public 的构造函数
2> 使用静态域 static 来维护实例,将单体对象作为单体类的一个静态域实例化。使用保存唯一实例的 static 变量,其类型就是单例类型本身。需要使用final,使其不能够被重载
注:采用懒汉方式来实例化该类
3> 使用静态方法来监视实例的创建
单例模式的实现
1> 加载时实例化
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static synchronized Singleton getInstance() {
return instance ;
}
}
2> 使用时实例化(惰性初始化):这样做可以在运行时收集需要的信息来实例化单体对象,确保实例只有在需要时才被建立出来
单例模式的一般方法
1> 定义一个类,它的构造函数为private,所有方位为 static
2> 定义一个类,它的构造函数为private,它有一个 static 的 private 的该类变量,在类初始化时实例化,通过一个 public 的 getInstance 方法获取它的引用,继而调用其中的方法
3> 定义一个类,它的构造函数为 private,它有一个 static 的private的boolean变量,用于表示是否有实例存在
单例模式的不同表现形式
1> 饿汉式单体类:类被加载的时候将自己初始化,更加安全些
2> 懒汉式单体类:在第一次被引用的时候将自己初始化,提高效率
3> 多例类(多例模式):除了可以提供多个实例,其它和单体类没有区别
多例实际上就是单例模式的自然推广,作为对象的创建模式,单例模式或多例类模式有以下特点:
1> 多例类可以有多个实例
2> 多例类必须自己创建,管理自己的实例,并向外界提供自己的实例。这种允许有限个或无限个实例,并向整个JVM提供自己实例的类叫做多例类,这种模式叫做多例模式
(1)有上限多例模式:有上限的多例模式已经把实例的上限当做逻辑的一部分,并建造到多例类的内部,这种多例模式叫做有上限多例模式
public class Singleton {
private static final Singleton instance1 = new Singleton();
private static final Singleton instance2 = new Singleton();
private Singleton() {
}
public static synchronized Singleton getInstance( int whichOne) {
if (whichOne == 1) {
return instance1 ;
} else {
return instance2 ;
}
}
}
(2)无上限多例模式:例数目没有上限的多例模式叫无上限多例模式
注:由于没有上限的多例类对实例的数目是没有限制的,因此,虽然这种多例模式是单例模式的推广,但是这种多例类并不一定能够回到单例类。于是先不知道要创建多少个实例,因此,必然使用聚集管理所有的实例
public class ShoppingCart {
private ShoppingCart shoppingCart = null ;
// 使用 HashMap 聚集管理所有的实例
private static HashMap<String, ShoppingCart> instance = new HashMap<String, ShoppingCart>();
// 订单列表
private ArrayList<ItemOrder> orderedItems = null ;
// 更新器
private int readCount = 0;
/**
* 私有的构造函数保证外界无法直接将此类实例化
*/
private ShoppingCart() {
orderedItems = new ArrayList<ItemOrder>();
}
/**
* 获取购物车,一个用户只能有一个购物车,有多少个用户就有多少个购物车
*
* @param user
* @return
*/
public synchronized static ShoppingCart getInstance(String user) {
ShoppingCart shoppingCart = instance .get(user);
if (shoppingCart == null) {
shoppingCart = new ShoppingCart();
instance .put(user, shoppingCart);
}
return shoppingCart;
}
/**
* 用户退出登录的时候,通过外部调用将购物车移除
*
* @param key
*/
public synchronized void removeShoppingCart(String key) {
instance .remove(key);
}
public class ItemOrder {
}
}
单例对象的同步(单例模式与多线程)
在多线线程模式下。惰性初始化会使多个线程同时初始化该单体,造成一个JVM中多个单例类型不一致。增加同步修饰符:public static synchronized Singleton getInstance() 这样就保证线程的安全性,这种处理方式虽然引入同步代码,但是因为这段代码会在最开始的时候执行一次或多次,所以对整个系统的性能不会造成影响
解决多线程在更新属性的时候,会造成属性的读写不一致
1> 读者/写着的处理方式,设置一个读计数器,每次读取信息前,将计数器加1,读完后将计数器减1。使用 notifyAll() 解除在该对象上调用wait 的线程阻塞状态。只有在读计数器为0时,才能更新数据,同时调用 wait() 方法要阻塞所有读属性的调用
2> 采用“影子实例”的办法,具体就是在更新属性时,直接生成另一个单例对象实例,这个新生成的单例对象实例将从数据库,文件或程序中读取最新的信息,然后将这些信息直接赋值给旧单例对象的属性
public class GlobalConfig {
private static GlobalConfig instance;
private Vector properties = null;
private boolean isUpdating = false;
private int readCount = 0;
private GlobalConfig() {
}
private static synchronized void syncInit() {
if (instance == null) {
instance = new GlobalConfig();
}
}
public static GlobalConfig getInstance() {
if (instance == null) {
syncInit();
}
return instance ;
}
public synchronized void update(String data) {
syncUpdateIn();
}
private synchronized void syncUpdateIn() {
while (readCount > 0) {
try {
wait();
} catch (Exception e) {
}
}
}
private synchronized void syncReadIn() {
readCount ++;
}
private synchronized void syncReadOut() {
readCount --;
notifyAll();
}
public Vector getProperties() {
syncReadIn();
// TODO Process data
syncReadOut();
return properties ;
}
}
单体模式适用场景
1> 区别工具类和单体类在于该类是否是有状态的。无状态化,提供工具性质的功能,那就是工具类。如果愿意的话,可以将单体类分为有他状态和无状态。有状态单体类又称为可变单体类,常用作状态库维护系统的状态。无状态单体类又称为不变单体类,常用作提供工具性质方法的对象
2> 是否承担唯一的责任,并且是否提供唯一的实例