最简单的设计模式--单例模式
我看的设计模式书是《Head First设计模式》,我决定不按照书上的章节顺序做笔记,按照我认为的容易理解程度从易到难来写。
一、单例模式的定义
单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。
二、实例说明
有一些对象其实我们只需要一个,例如:线程池(threadpool)、缓存(cache)、对话框、处理器偏好设置和注册表(registry)的对象、日志对象等。
2.1 一个最简单的经典单例模式代码
我们可以用Java的静态变量来坐到这一点,但事静态全局变量在程序一开始就被创建好对象,如果这个对象非常耗费资源,而程序在这次执行过程中又一次没用到它,就形成浪费。
经典的单例模式代码:
1 public class Singleton { 2 private static Singleton uniqueInstance; 3 private Singleton(){}//构造器声明为私有,只有类内才可以调用构造器 4 public static Singleton getInstance(){ 5 if(uniqueInstance==null){ 6 uniqueInstance=new Singleton(); 7 } 8 return uniqueInstance; 9 } 10 }
别的类用要用这个类的对象的话,就通过Singleton.getInstance()来获取,if判断而且确保了这个类只有一个实例化的对象。这样似乎都一切正常了。不过实际项目中,肯定会有多线程的场景,那样就可能产出两个实例。例如这里有两个线程,线程1运行到上述代码第5行时候,new 一个对象,假如此刻线程2也进入5行,发现此时uniqueInstance为null,它也new 一个对象,那么就产生了两个实例化对象了。所以,要在第4行上面加上synchronized,这样就保证了不会存在两个线程同时进入到getInstance方法。
2.2改善多线程
在2.1中我们说过在getInstance方法加入synchronized关键字来解决多线程会实例化多个对象的问题,它也存在一些问题:
1)同步会降低性能;
2)更严重的是:上述代码只要第一次执行getInstance方法时,才真正需要同步。换句话说,一旦设置好uniqueInstance变量,就不再需要同步这个方法了。之后每次调用这个方法,同步都是一种累赘。
下面给出解决三种解决方案:
2.2.1 如果getInstance()的性能对应用程序不是很关键,就什么都别做
没错,就是这么直接!当然,如果getInstance()在程序中频繁的运行,那就得重新设计了。
2.2.2 使用“急切”创建实例,而不用延迟实例化的做法
如果程序总是会创建并使用到这个单例类,或者创建这个单例类的实例不繁重,可以急切的创建此单例:
public class Singleton { private static Singleton uniqueInstance=new Singleton();//在静态初始化中创建单例 private Singleton(){} public static Singleton getInstance(){ return uniqueInstance; } }
2.2.3 用“双重检查加锁”,在getInstance()中减少使用同步
首先检查是否实例已经创建了,如果没有,“才”进行同步。这样一来,只有第一次创建会用到同步,这正是我们想要的。
1 public class Singleton { 2 private volatile static Singleton uniqueInstance; 3 private Singleton() {} 4 public static Singleton getInstance() { 5 if (uniqueInstance == null) { 6 synchronized (Singleton.class){ 7 if(uniqueInstance == null){//进入区域后,再检查一次,如果仍是null,才创建实例 8 uniqueInstance = new Singleton(); 9 } 10 } 11 } 12 return uniqueInstance; 13 } 14 }
第7行还需要加入一次判断,有可能别的线程在此线程拿5-6行的期间,已经实例化了。(ps:我的个人理解)关于volatile关键字,不清楚的同学可以去查,以后等我看完JVM,会专门写一篇volatile关键字的文章。
三、单例模式的延伸
1、听说两个类加载器可能会各自创建自己的单例对象?
答:是的。每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会同时加载同一个类,从整个程序来看,同一个类会被加载多次。如果这样的事情发生在单件上,就会产生多个单例并存的现象。所以,如果程序有个多个类加载器又同时使用了单例模式,可以这样解决:自行指定类加载器,并指定同一个类加载器。
2、全局变量比单例模式差在哪里?
答:1)不可延迟实例化;2)并不能确保只有一个实例,而且也变相鼓励开发人员,用许多全局变量指向许多小对象来造成命名空间的污染。