一、单例的使用场景:
1)需要频繁的进行创建和销毁的对象
2)创建对象消耗时过多或耗资源过多,但又经常用到的对象
3)工具类对象
4)数据库的连接池也采用单例模式,因为要频繁的访问数据库对象
5)一些共享的配置文件,比如spring的配置文件的读取
6)多线程的线程池的设计也是采用单例模式,由于线程池要方便对池中的线程进行控制
7)网站计数器,一般也是采用单例模式,否则难以实现同步
二、单例的优点:
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类在实例化进程上有相应的伸缩性
3.提供了对唯一实例的访问入口
4.由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能
5.允许可变数目的实例(可以根据实际情况需要,在单例模式的基础上扩展做出双例模式、多例模式)
6.避免对共享资源的多重占用
三、单例的缺点:
1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态
2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
3.单例类的职责过重,在一定程度上违背了“单一职责原则”
4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失
四、单例的实现
单例模式实现的方式有:懒汉式、饿汉式、双重校验锁、静态内部类、枚举法以及各自的变种方式
推荐使用的实现方式:双重校验锁(基于懒汉式)、静态内部类、枚举法
1.一般写法:懒汉式
以下普通写法,在单线程中没问题,但是如果在多线程中,
当一个线程进入判断语句if(null==singletonOne) 时会发生线程阻塞,
此时第二个线程进来了,创建了一个实例,当阻塞线程被唤醒时,就会又创建一个对象实例,显然与单例模式不符合
问题:单线程下没问题,多线程下存在创建多个实例问题,不采用
package com.creational.singleton;
public class SingletonOne {
private static SingletonOne singletonOne;
private SingletonOne() {
}
public static SingletonOne getInstance1() {
if(null==singletonOne) {
singletonOne = new SingletonOne();
}
return singletonOne;
}
}
2.一般写法:饿汉式
原理:声明一个私有的构造方法,创建一个私有的静态实例,公有的获取实例的方法
问题:如果该对象的构造函数很复杂的话,类加载时就会耗费很多性能,不采用
package com.creational.singleton;
public class SingletonTwo {
private static SingletonTwo instance = new SingletonTwo();
public static SingletonTwo getInstance1() {
return instance;
}
}
3.多线程下实现:同步锁–基于懒汉式
问题:下面代码还是存在性能问题,因为同步锁机制,多个线程获取类实例对象会排队等待获取锁,这样是没有必要的,因为大多数情况下类实例对象已经创建成功了,所以不用进入加锁代码块
package com.creational.singleton;
public class SingletonOne {
private static SingletonOne singletonOne;
public static SingletonOne getInstance2() {
synchronized (SingletonOne.class) {
if(null==singletonOne) {
singletonOne = new SingletonOne();
}
}
return singletonOne;
}
}
4.多线程下实现:双重校验锁–基于懒汉式
这种写法能够做到效率和安全的双重保证,适用于jdk1.5+
推荐使用
package com.creational.singleton;
public static SingletonOne getInstance3(){
private static SingletonOne singletonOne;
private SingletonTwo() {}
if(null == singletonOne){
synchronized(SingletonOne.class){
if(null ==singletonOne){
singletonOne = new SingletonOne ();
}
}
}
}
5.多线程下实现:静态代码块–基于饿汉式
在静态代码块中去创建实例,其实实现方式跟一般饿汉式差不多,都是在类加载的时候创建实例,没有达到懒加载的机制,不采用
package com.creational.singleton;
public class SingletonTwo {
private static SingletonTwo instanceEx = null;
private SingletonTwo() {}
static {
instanceEx = new SingletonTwo();
}
public static SingletonTwo getInstance2() {
return instanceEx;
}
}
6.多线程下实现:静态内部类方法
原理:同样利用了classLoader的机制来保证初始化instance时只有一个线程。比较饿汉式的两种方式,其实看起来差别不大,但是使用静态内部类:当SingletonThree 被装载时,静态内部类SingletonClass并没有被类加载器加载,因此instance实例并没有被初始化,只有当外部调用getInstance()方法时才会去装载静态内部类,此时静态成员变量就会被加载,而且只加载一次,进而达到了懒加载机制,并且实现了单例模式。
推荐使用
package com.creational.singleton;
public class SingletonThree {
private static class SingletonClass {
private static SingletonThree instance = new SingletonThree();
}
public static SingletonThree getInstance() {
return SingletonClass.instance;
}
}
7.多线程下实现:枚举实现
//最简洁、易用的单例实现方式,(《Effective Java》推荐)
package com.designmode;
public enum Singleton{
//定义一个枚举的元素,它就是Singleton的一个实例
INSTANCE;
private Singleton() {
}
public void doSomething(){
}
}
调用方式:
Singleton.INSTANCE.doSomething();