java的设计模式---单例模式

    1.1  单例模式的定义

单例模式的英文原话是:

Ensure a class has only one instance, and provide a global point of access to it.

意思是:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式主要作用是确保一个类只有一个实例存在。单例模式可以用在简历目录、数据库连接等需要单线程操作的场合,用于实现对系统资源的控制。

由于Java语言的特点,是的在Java中实现单例模式通常有两种表现形式。

1、饿汉式单例类:类加载时,就进行对象实例化;
2、懒汉式单例类:第一次引用类时,才进行对象实例化。

1、饿汉式单例类

饿汉式单例类的源代码如下所示。
class Singleton {
	private static Singleton instance = new Singleton();
	//构造方法私有,保证外界无法直接实例化
	private Singleton() {}
	//通过该方法获得实例对象
	public static Singleton getInstance() {
		return instance;
		}
}
     
      从上述代码中可以看到,在类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,
单例类的唯一实例会被创造出来。单例类中一个最重要的特点是类的构造函数是私有的,从而避免外界利用构造
函数直接创建出任意多的实例。另外需要的事,由于构造函数是私有的,因此该类不能被继承。

2、懒汉式单例类

       懒汉式单例类与饿汉式单例类相同的是,类的构造函数是私有的;不同的是,懒汉式单例类在加载时不会将

自己实例化,而是在第一次被调用时自己实例化。

懒汉式单例类的源代码如下所示。

class Singleton {
	private static Singleton_1 instance = null;
	//构造方法私有,保证外界无法直接实例化
	private Singleton() {}
	//方法同步
	synchronized public static Singleton getInstance() {
		if(instance == null)
			instance  = new Singleton_1();
		return instance;
	}
}


       上述代码中,懒汉式单例类中对静态方法getInstance()进行同步,以确保多线程环境下只创建一个实例,

例如,如果getInstance()方法未被同步,并且线程A和线程B同时调用此方法,则执行if(instance == null)语句

时为真,那么线程A和线程B都会创建一个对象,在内存中就会出现两个对象,这样就违背了单例模式;但是

使用了synchronized关键字进行同步后,则不会出现此情况。


饿汉式单例类和懒汉式单例类之间的区别如下。

1、饿汉式单例类在被加载时实例化,而懒汉式单例类在第一次引用时实例化;

2、从资源利用效率上说,饿汉式单例类要差一些,但从速度和反应时间的角度来说,饿汉式单例类表现的稍好一些;

3、饿汉式单例类可以在Java中实现,但不易在C++内实现。GoF在提出单例模式的概念时,举得例子是懒汉式,他们

     的书影响之大,以致Java中单例类的例子也太多是懒汉式。实际上,饿汉式更符合Java语言本身的特点。


1.2   单例模式的应用

1、单例模式的优点

1、由于单例模式在内存只有一个实例,减少了内存的开支,特别是一个对象需要频繁地创建、销毁,而且创建或者

      的性能又无法优化时,单例模式的优势就非常明显。

2、由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多资源时,如读取配置、

     产生其他依赖对象时,则可以通过在启用时直接产生一个单例对象,然后用永久驻留内存的方式来解决。

3、单例模式可以避免对资源的多重占用,例如,一个写文件动作,由于只有一个实例存在内存中,避免了对同一个

     资源文件的同时操作。

4、单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的

     映射处理。


2、单例模式的缺点

1、单例模式无法创建子类,扩张困难,若要扩展,除了修改代码基本没有第二种途径可以实现。

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

      通常不会实现接口,这妨碍了使用mock的方法虚拟一个对象。

3、单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关系它是否是单例的,是不是要用单例模式

     取决于环境,单例模式把“要单例”和业务逻辑融合在一起。


3、单例模式的使用场景

1、要求生产唯一序列号的环境。

2、在整个项目需要一个共享访问点火共享数据,例如,一个web页面上的计数器,可以不用把每次刷新都记录到数

     据库中,使用单例模式保持计数器的值,并确保线程的安全。

3、创建一个对象需要消耗的资源过多,如访问IO和数据库等资源

4、需要定义大量的静态常量和静态方法的环境,可以采用单例模式。


4、单例模式的实例

使用单例模式吉利访问次数。
其代码如下所示。
public class Text {

	public static void main(String[] args) {
		NumThread threadA = new NumThread("线程A");
		NumThread threadB = new NumThread("线程B");
		//启动线程
		threadA.start();
		threadB.start();
	}

}
class GlobalNum{
	private static GlobalNum gn = new GlobalNum();
	private int num = 0;
	public static GlobalNum getInstance(){
		return gn;
	}
	public synchronized int getNum(){
		return ++num;
	}
}
class NumThread extends Thread{
	private String threadName;
	public NumThread(String name){
		threadName = name;
	}
	//重写线程的run方法
	public void run(){
		GlobalNum gnObj = GlobalNum.getInstance() ;
		for(int i = 0; i < 5; i++){
			System.out.println(threadName + "第" + gnObj.getNum() + "次访问!");
			try {
				//线程休眠1000毫秒
				this.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}

      上述代码在主程序中创建两个子线程,通过两个子线程演示对单例模式唯一实例的访问。因为
GlobalNum的对象是单例的,所以能够统一地对线程访问次数进行统计。
       其结果如下所示。
线程A第1次访问!
线程B第2次访问!
线程B第4次访问!
线程A第3次访问!
线程B第5次访问!
线程A第6次访问!
线程A第7次访问!
线程B第8次访问!
线程A第9次访问!
线程B第10次访问!


文章内容出自《设计模式》一书,有兴趣的可以去购买看看。

每周日将更新一个新的设计模式,下一篇是工厂设计模式。

有错误问题可以发信息到我的邮件 550569627@qq.com







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值