设计模式之01单例模式(笔记)

本文详细介绍了单例模式的概念、优缺点及其应用场景。通过代码示例展示了如何实现单例模式,并讨论了其在并发环境下的线程同步问题及对象复制情况。

1 定义:

1.1文字定义:Ensure a classhas only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)

1.2通用类图:


图1 单例模式通用类图

1.3通用代码:

public class Singleton {
         privatestatic final Singleton singleton = new Singleton();
        
         //限制产生多个对象
         privateSingleton(){
         }
        
         //通过该方法获得实例对象
         publicstatic Singleton getSingleton(){
                   returnsingleton;
         }
        
         //类中其他方法,尽量是static
         publicstatic void doSomething(){
         }
}

 

2优点:

  • 内存仅有一个实例,减少内存开支;特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  • 当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永驻留内存的方式来解决(在Java EE中注意JVM垃圾回收机制)。
  • 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

 

3缺点:

3.1 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有别的途径。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例。接口、抽象类是不可能被实例化的。当然特殊情况下,单例模式可以实现接口、被继承等,需要在系统开中根据环境判断。

3.2 单例模式对测试不利。在并行开发环境中,如果单例没有完成,是不能进行测试的。

3.3 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。


4使用场景:

在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体场景如下:

  • 要求生成唯一序列号的环境;
  • 在整个项目中需要一个共享访问点或共享数据,如计数器;
  • 创建一个对象需要消耗资源过多,如要访问IO和数据库等资源;
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式。

 

5注意事项:

5.1 高并发时注意单例模式的线程同步问题。

         在单例的实例化时,有“饿汉式”如1.3中实例化方法;也有使用同步方法,仅在需要时创建的方式,这种方式称为“懒汉式”单例;后者不是优秀的方式,前者为推荐方式。

         若后者未添加同步关键字,则可能导致“创建 赋值”过程中,被另一调用再次创建。

5.2 考虑对象复制情况。

         在Java中,对象默认是不可以被复制的,若实现了Cloneable接口,并实现了clone方法,则可以直接通过对象复制方式创建一个新对象,对象复制不调用类的构造函数,因此即使是私有的构造函数,对象仍然可以被复制。即,单例类不要实现Cloneable接口。

  

6扩展:

         如果一个类只要一个对象,使用单例模式即可;如果一个类可以只要两个、三个对象呢?亦可参照单例模式,静态初始若干实例,然后使用时或随机或按某种规则取出即可。

  

7范例:

 单例用法(计数器类,部分工具类)

 

package _01_Singleton;

public class Counter {
	private static final Counter counter = new Counter();//创建实例 
	private static int total = 0;
	private Counter(){}
	
	public static Counter getInstance(){//获取实例
		return counter;
	}
	
	//做点事。
	public synchronized void Increase(){
		total++;
	}
	//做点事。
	public synchronized int getTotal(){
		return total;
	}
	
	//工具类可参考
	public static void whatAmI(){
		System.out.println("我是计数器工具方法");
	}
}
测试:

 

package _01_Singleton;

public class TestCounter {

	public static void main(String[] args) {
		Counter.whatAmI();
		Counter c = Counter.getInstance();
		for(int i=0; i<10; i++){
			c.Increase();
			System.out.println("The current total is : " + c.getTotal());
		}
	}
}
结果:

我是计数器工具方法
The current total is : 1
The current total is : 2
The current total is : 3
The current total is : 4
The current total is : 5
The current total is : 6
The current total is : 7
The current total is : 8
The current total is : 9
The current total is : 10

 

注意:

使用单例模式需要注意的一点就是JVM的垃圾回收机制,如果我们的一个单例对象在内存中长久不使用,JVM就认为这个对象是一个垃圾,在CPU资源空闲的情况下,该对象会被清理掉,下次再调用时就需要重新产生一个对象。如果我们在应用中使用单例类作为有状态值(如计数器)的管理,则会出现恢复原状的情况,应用就会出现故障。如果确实需要采用单例模式来记录有状态的值,有两种方法可以解决该问题:(下述为转述,未尝试

由容器管理单例的生命周期

JavaEE容器或者框架级容器(如Spring)可以让对象长久驻留内存。当然,自行通过管理对象的生命期也是一个可行的办法,既然有那么多的工具提供我们,为什么不用呢?

状态随时记录

可以使用异步记录的方式,或者使用观察者模式,记录状态的变化,写入文件或写入数据库中,确保即使单例对象重新初始化也可以从资源环境获得销毁前的数据,避免应用数据丢失。


评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值