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