一、用通俗的语言解释其意义:从秦始皇之后确立了皇帝的位置,且同一时期只有一个。因此大家在称呼的时候不需要为皇帝加上其他的前缀。这一过程反应在设计领域就是,要求一个类只能生成一个对象,所有对象对他的依赖都是相同的,因为只有一个对象,所以对象对他的依赖都是相同的,因为只有一个对象,大家对他都很了解。皇帝每天要处理很多的事情,但是皇帝只有一个,即一个类只有一个对象,对象产生通过new关键字完成,我们可以使用构造函数来控制,因为使用new关键字创建对象时都会根据输入的参数调用相应的构造函数,所以如果我们将构造函数的访问权限设置为private就可以进制外部创建对象了。
将以上的情景设置为简单的例子:
皇帝类:
1 public class Emperor{ 2 private static final Emperor emperor=new Emperor();//初始化一个皇帝 3 private Emperor(){ 4 } 5 public static Emperor getInstance(){ 6 return emperor; 7 } 8 public static void say(){ 9 System.out.println("我的就是皇帝"); 10 } 11 }
通过定义私有的访问权限的构造函数,避免被其他类new出一个对象,而Emperor自己则可以new一个对象处理,其他类都可以访问getInstance获得同一个对象。
大臣类:
1 public class Minister{ 2 public static void main(String[]args){ 3 for(int day=0;day<3;day++){ 4 Emperor emperor=Emperor.getInstance(); 5 emperor.say(); 6 } 7 } 8 }
二、单例模式的定义
单例模式(Singleton Pattern):Ensure class has only one instance,and provide a global point of access to it.
Singleton类称为单例类,通过使用private的构造函数确保在一个应用中只产生一个实例。并且是自己实例化的(在Singleton中使用new Singleton)。
单例模式的通用代码:
1 public class Singleton{ 2 private static final Singleton singleton=new Singleton(); 3 private Singleton(){} 4 public static Singleton getSingleton(){ 5 return singleton; 6 } 7 public static void doSomething(){} 8 }
三、单例模式的应用
1、单例模式的优点
由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建和销毁时,而且创建或销毁的性能又无法优化,单例模式的优势就非常明显。
由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
2、单例模式的缺点
单例模式一般没有接口,扩展比较困难,若要扩展,除了修改没有第二个办法。单例模式为什么不能增加接口?因为接口对单例模式是没有任何意义的,他要求自行实例化,并且提供单一实例。接口或抽象类是不能被实例化的。
单例模式对测试是不利的,在单例模式没有完成之前,是不能进行测试的。
单例模式与单一职责原则是有冲突的,一个类应该只实现一个逻辑,而不关心她是否单例,是不是单例要取决于环境,单例模式将要单例和业务逻辑融合在一个类里。
3、单例模式的使用场景
要求生成唯一序列号的环境;
在整个项目中需要一个共享访问点或共享数据,例如一个web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式爆出加护器的值,并确保线程安全。
创建一个对象需要消耗资源过多,如要访问IO和数据库等资源
需要定义大量的静态常量和静态方法如工具类等。
4、单例模式的注意事项
在高并发的情况下,要注意单例模式的线程同步问题
线程不安全的单例:
1 public class Singleton{ 2 public static Singleton singleton=null; 3 private Singleton(){} 4 public static Singleton getSingleton(){ 5 if(singleton==null){ 6 singleton=new Singleton(); 7 } 8 return singleton; 9 } 10 }
该单例模式在低并发的情况下是没有问题的,在并发增多时,则可能在内存中存在多个实例,破坏最初的预期。例如线程A执行到singleton=new Singleton()但是没有获得对象,此时第二个线程也开始判断平执行,解决线程不安全的方法很多,可以在getSingleton方法前面加上synchronized来实现;还要考虑对象的复制情况。在java中默认是不可以复制的,若实现的Clonable接口,并实现了clone方法则可以直接通过复制方式创建一个新对象,对象复制是不调用构造函数的,因此即使是私有构造函数依然可以复制。一般很少单例类会主动要求被复制,因此不要实现Clonable接口。
四、单例模式的扩展
我们之前看到的都是单例创建单一对象。如果要多于一个对象该如何。
固定数量的皇帝类:
1 public class Emperor{ 2 //定义最多能产生的实例实例 3 private static int maxNumOfEmperor=2; 4 //每个皇帝都有名字,使用一个ArrayList来容纳,每个对象的私有属性 5 private static ArrayList<String> nameList=new ArrayList<String>(); 6 //定义一个列表,容纳所有的皇帝实例 7 private static ArrayList<Emperor> emperorList=new ArrayList<Emperor>(); 8 //当前皇帝序列号 9 private static int countNumOfEmperor=0; 10 //产生所有的对象 11 static{ 12 for(int i=0;i<maxNumOfEmperor;i++){ 13 emperorList.add(new Emperor("皇"+i+"帝")); 14 } 15 } 16 private Emperor(){ 17 } 18 private Emperor(String name){ 19 nameList.add(name);} 20 public static Emperor getInstance(){ 21 Random random=new Random(); 22 countNumOfEmperor=random.nextInt(maxNumOfEmperor); 23 return emperorList.get(countNumOfEmperor); 24 } 25 public static void say(){ 26 System.out.println(nameList.get(countNumOfEmperor));} 27 28 }
大臣类:
1 public class Minister{ 2 public static void main(String[]args) 3 { 4 int ministerNum=5; 5 for(int i=0;i<ministerNum;i++){ 6 Emperor emperor=Emperor.getInstance(); 7 System.out.println("第"+(i+1)+"个达成拜的是:"); 8 emperor.say(); 9 } 10 } 11 }
这种也叫作有上限的多例模式。
五、实践
在Spring中。每个Bean默认就是单例的,这样做的优点是Spring容器可以管理他们的生命周期,创建 销毁等。如果没有采用非单例模式。则Bean初始化之后的管理交由J2EE容器,Spring容器不再跟踪管理Bean的生命周期。