一,什么是单体模式
对象只要利用自己的属性完成了自己的任务,那该对象就是承担了责任。除了维持了自身的一致性,该对象无需承担其他任何责任。 如果该对象还承担着其他责任,而其他对象又依赖于该特定对象所承担的责任,我们就需要得到该特定对象。
将类的责任集中到唯一的单体对象中,确保该类只有一个实例,并且为该类提供一个全局访问点。这就是单体模式的目的。
单体模式的难点不在于单体模式的实现,而在于在系统中任何识别单体和保证单体的唯一性。
二,单体模式的实现
1,提供唯一的私有构造器,避免多个单体(Singleton)对象被创建,这也意味着该单体类不能有子类,那声明你的单例类为final是一个好主意,这样意图明确,并且让编译器去使用一些性能优化选项。 如果有子类的话使用protected,protected的构造方法可以被其子类以及在同一个包中的其它类调用。私有构造器可以防止客户程序员通过除由我们提供的方法之外的任意方式来创建一个实例,如果不把构造器声明为private或protected, 编译器会自动的创建一个public的构造函数。
2,使用静态域(static field)来维护实例。
将单体对象作为单体类的一个静态域实例化。 使用保存唯一实例的static变量,其类型就是单例类型本身。需要的话使用final,使其不能够被重载。
例如:private static Rutime currentRuntime = new Runtime();
3,使用静态方法(Static Method)来监视实例的创建。
a,加载时实例化
例如:
public class Singleton {
private static final Singleton Singleton _instance = new Singleton();
private Singleton() {
}
public static synchronized Singleton getInstance() {
return Singleton _instance;
}
}
b,使用时实例化(惰性初始化):这样做可以在运行时收集需要的信息来实例化单体对象,确保实例只有在需要时才被建立出来。
例如:
public class Singleton {
private static final Singleton Singleton _instance = null;
private Singleton() {
//使用运行时收集到的需要的信息,进行属性的初始化等操作。
}
public static synchronized Singleton getInstance() {
if (Singleton _instance == null){
Singleton _instance = new Singleton();
}
return Singleton _instance;
}
}
4,单体对象的成员变量(属性):即单体对象的状态 通过单例对象的初始化来实现成员变量的初始化。 通过方法对单体对象的成员变量进行更新操作。
例如:
public class Singleton {
private static final Singleton Singleton _instance = null;
private Vector properties = null;
protected Singleton() {
//使用运行时收集到的需要的信息,进行属性的初始化等操作。
}
private static synchronized void syncInit() {
if (Singleton _instance == null) {
Singleton _instance = new Singleton();
}
}
public static Singleton getInstance() {
if (Singleton _instance == null){
syncInit();
}
return Singleton _instance;
}
public synchronized void updateProperties() {
// 更新属性的操作。
}
public Vector getProperties() {
return properties;
}
}
三,单体对象的同步(单体模式与多线程)
1,单体对象初始化同步
问题: 在多线程模式下,惰性初始化会使多个线程同时初始化该单体,造成一个JVM中多个单例类型的实例, 如果这个单例类型的成员变量在运行过程中发生变化,会造成多个单例类型实例的不一致。
解决的办法:(上面已经给出了例子) 加个同步修饰符: public static synchronized Singleton getInstance(). 这样就保证了线程的安全性. 这种处理方式虽然引入了同步代码,但是因为这段同步代码只会在最开始的时候执行一次或多次,所以对整个系统的性能不会有影响。
2,单体对象属性的同步
问题: 在更新属性的时候,会造成属性的读写不一致。
解决方法:
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() {
//Load configuration information from DB or file
//Set values for properties
}
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 p_data) {
syncUpdateIn();
//Update properties
}
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();
//Process data
syncReadOut();
return properties;
}
}
四,单体模式的不同表现形式
不同表现形式:
1,饿汉式单体类:类被加载的时候将自己初始化。更加安全些。
2 ,懒汉式单体类:在第一次被引用的时候将自己初始化。提高了效率。
3,多例类(多例模式):除了可以提供多个实例,其他和单体类没有区别。
五,单体模式的不同表现形式之:多例类(多例模式)
所谓多例(Multiton Pattern)实际上就是单例模式的自然推广。作为对象的创建模式,多例模式或多例类有以下的特点:
a、多例类可以有多个实例
b、多例类必须自己创建,管理自己的实例,并向外界提供自己的实例。 这种允许有限个或无限个实例,并向整个JVM提供自己实例的类叫做多例类,这种模式叫做多例模式
1,有上限多例模式 有上限的多例类已经吧实例的上限当作逻辑的一部分,并建造到了多例类的内部,这种多例模式叫做有上限多例模式。
import java.util.Random;
import java.util.Date;
public class Die
{
private static Die die1 = new Die();
private static Die die2 = new Die();
/**
*私有的构造函数保证外界无法直接将此类实例化
*
*/
private Die()
{
}
/**
*工厂方法
*/
public static Die getInstance(int whichOne)
{
if(whichOne==1)
{
return die1;
}else{
return die2;
}
}
/**
*投骰子,返回一个在1-6之间的随机数
*/
public synchronized int dice()
{
Date d = new Date();
Random r = new Random(d.getTime());
int value = r.nextInt();//获取随机数
value = Math.abs(value);//获取随机数的绝对值
value = value % 6;//对6取模
value +=1;//由于 value的范围是0-5,所以value+1成为1-6
return value;
}
}
/**
*测试的客户端,投掷骰子
*/
public class DieClient
{
private static Die die1,die2;
public static void main(String[] args)
{
die1 = Die.getInstance(1);
die2 = Die.getInstance(2);
System.out.println(die1.dice());
System.out.println(die2.dice());
}
}
2,无上限多例模式 实例数目没有上限的多例模式叫无上限多例模式。 注意:由于没有上限的多例类对实例的数目是没有限制的,因此,虽然这种多例模式是单例模式的推广,但是这种多例类并不一定能够回到单例类。 由于事先不知道要创建多少个实例,因此,必然使用聚集管理所有的实例。
例子:购物车
import java.util.ArrayList;
import java.util.HashMap;
public class ShoppingCart {
//private ShoppingCart shoppingCart = null;
//使用HashMap聚集管理所有的实例;
private static HashMap<String, ShoppingCart> instanse = new HashMap<String, ShoppingCart>();
//订单列表
private ArrayList<ItemOrder> orderedItems = null;
//更新器
private int readCount = 0;
/**
*同单例类一样,私有的构造函数保证外界无法直接将此类实例化
*
*/
private ShoppingCart() {
orderedItems = new ArrayList<ItemOrder>();
}
/*
* 获取购物车,一个用户只能有一个购物车。有多少用户就有多少购物车。
* */
public synchronized static ShoppingCart getInstance(String user) {
ShoppingCart shoppingCart = instanse.get(user);
if (shoppingCart == null){
shoppingCart = new ShoppingCart();
instanse.put(user, shoppingCart);
}
return shoppingCart;
}
/*
* 用户退出登陆的时候,通过外部调用将购物车移除。
* */
public synchronized void removeShoppingCart(String key){
instanse.remove(key);
}
/*
* 获取购物车中订单列表(orderedItems)
* */
public ArrayList<ItemOrder> getOrderedItems() {
readIn();
readOut();
return orderedItems;
}
/*
* 管理订单。
* 如果是旧订单则更新其数量。
* 如果是新订单则添加到订单列表中。
* */
public void addItem(String itemId){
updateIn();
ItemOrder order;
for(int i=0; i<orderedItems.size(); i++) {
order = (ItemOrder)orderedItems.get(i);
if ((order.getItem().getItemID()).equals(itemId)) {
order.incrementNumItems();
return;
}
}
ItemOrder newOrder = new ItemOrder(Catalog.getItem(itemId), 1);
orderedItems.add(newOrder);
}
/*
* 管理订单数量。
* 如果是旧订单,其数量为0则移除该订单,否则更新其数量。
* 如果是新订单,则添加到订单列表中。
* */
public synchronized void setNumOrdered(String itemId,int numOrdered){
updateIn();
ItemOrder order;
for(int i=0; i<orderedItems.size(); i++) {
order = (ItemOrder)orderedItems.get(i);
if ((order.getItem().getItemID()).equals(itemId)) {
if (numOrdered <= 0) {
orderedItems.remove(i);
} else {
order.setNumItems(numOrdered);
}
return;
}
}
ItemOrder newOrder = new ItemOrder(Catalog.getItem(itemId), 1);
orderedItems.add(newOrder);
}
private synchronized void updateIn() {
while (readCount > 0) {
try {
wait();
} catch (Exception e) {
}
}
}
private synchronized void readIn() {
readCount++;
}
private synchronized void readOut() {
readCount--;
notifyAll();
}
}
六,识别单体模式
1,区别工具类和单体类在于该类是否是有状态的。无状态化,提供工具性质的功能,那就是工具类。
如果愿意的话,你可以将单体类分为有他状态和无状态。
有状态单体类又称为可变单体类,常用作状态库维护系统的状态。
无状态单体类又称为不变单体类,常用作提供工具性质方法的对象。
2,是否承担了唯一的责任,并且是否提供了唯一的实例。
七,与单体模式相关的模式
在抽象工厂模式中可以使用单体模式,将具体工厂类设计为单体类。建造模式可以使用单例模式,将具体类设计成单体类。
八,单体模式的应用
建立目录,数据库连接或 Socket 连接要受到一定的限制,必须保持同一时间只能有一个连接的存在等这样的 单线程 操作。 使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。
1, 关于数据库连接应用单体模式的问题:
关于把单例模式应用到数据库connection的问题较为复杂,如果是简单地把一个connection对象封存在单例对象中,那么在J2EE环境中这是错误的。
如果数据库连接做成单例模式也就是说系统只会存在一个数据库连接实例,大家公用。实例不可以并发使用。因此存在排队,单例模式管理是需要排队的。 资源有两种,一种需要排队,一种不需要排队,或者需要一种复杂的排队,譬如数据库就是复杂的排队问题,系统的排队是不可避免的,应该由数据库引擎自行解决。解决方式就是纪录的locking,而locking不应该由Java程序解决。尽量将排队的工作交给更低一层来做,这样可以获得更高的效率。
但是在单用户系统中这并不是什么严重的问题,因为在某一个时刻只有一个用户在使用,唯一的问题就是系统可能需要几个connection,譬如两个、三个等,而不是一个。 J
2EE服务器系统中单例模式可以用来管理一个数据库连接池(connection pool)。单例模式可以用来保存这样一个connection pool,在初始化的时候创建譬如100个connection对象, 然后再需要的时候提供一个,用过之后返回到pool中。如果不是用单例模式的话,这个pool存在哪里,就是一个问题。最后可能只好存到Application对象中。
2,关于"全局"变量的问题。
使用单例模式来存放"全局"变量是违背单例模式的用意的,单例模式只在有真正的“单一实例”的需求时才可以使用。
其次,一个设计得当的系统不应该有所谓的"全局"变量的。这些变量应该放到他们所描述的实体所对应的类中去。
将这些变量从他们所描述的实体类中抽出来,放到一个不相干的单体类中去,使得这些变量产生错误的依赖关系和耦合关系。
所以,如果需要的话,我们可以将一个承担了责任的类作为一个单体类来实现,而不仅仅是为了一个“全局”变量。
3,有时使用Singleton并不能达到Singleton的目的,如有多个Singleton对象同时被不同的类装入器装载;在EJB这样的分布式系统中使用也要注意这种情况,因为EJB是跨服务器,跨JVM的。
总之:Singleton模式看起来简单,使用方法也很方便,但是真正用好,是非常不容易,需要对Java的类 线程 内存等概念有相当的了解。 如果你的应用基于容器,那么Singleton模式少用或者不用,可以使用相关替代技术。