Java设计模式——Singleton单例模式
Singleton单例模式根据产生时机不同,可分为如下三类:
(1)懒汉式(2)饿汉式(3)登记式
在Java设计模式中,单例模式相对来说算是比较简单的一种构建模式。适用场景:对于定义的一个类,在整个应用程序执行期间只有唯一的一个实例对象。
通过单例模式,自行实例化并向这个系统提供这个单一实例的访问方法,看以下代码:
/** 懒汉式——
其特点是延迟加载,即当需要用到此单一实例的时候,才去初始化此单一实例。**/
//常见经典的写法如下:
package Paint;
public class SingleTon {
// 静态实例变量
private static SingleTon instance;
// 私有化构造函数
private SingleTon() {
}
// 静态public方法,向整个应用提供单例获取方式
public static SingleTon getInstance() {
if (instance == null) {
instance = new SingleTon();
}
return instance;
}
}
/** 如上代码线程并不安全,懒汉式的线程安全写法为: **/
package Paint;
public class SingleTon {
// 静态实例变量加上volatile
private static volatile SingleTon instance;
// 私有化构造函数
private SingleTon() {
}
// 双重检查锁
public static SingleTon getInstance() {
if (instance == null) {
synchronized(SingleTon.class){
if(instance == null){
instance = new SingleTon();
}
}
}
return instance;
}
}
/** 饿汉式——
饿汉式的特点是应用中尚未需要用到此单一实例的时候即先实例化。**/
// 常见的经典写法为:
package Paint;
public class SingleTon {
// 静态实例变量,直接初始化
private static SingleTon instance = new SingleTon();
// 私有化构造函数
private SingleTon() {
}
// 静态public方法,向整个应用提供单例获取方式
public static SingleTon getInstance() {
return instance;
}
}
/**登记式单例模式——
登记式单例模式,一般是通过一个专门的类对各单例模式的此单一实例进行管理和维护,通过Map方式可以方便的实现此中目的。**/
// 常见代码如下:
package Paint;
import java.util.HashMap;
import java.util.Map;
public class SingleTonManager {
private static Map singleTonMap = new HashMap();
public static void main(String[] args) {
A a = (A) getInstance(A.class.getName()); // 获取A类的单例
B b = (B) getInstance(B.class.getName()); // 获取B类的单例
}
// 根据类型获取单例
public static Object getInstance(String className) {
// 判断singleTonMap中是否有此单例,有则取得后返回,无则添加单例后返回
if (!singleTonMap.containsKey(className)) {
try {
singleTonMap.put(className, Class.forName(className).newInstance());
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
}
return singleTonMap.get(className);
}
}
class A {
}
class B {
}
PS:需要注意的是,在多线程环境中,以上各种方法构造单例模式需要考虑到线程的安全问题。
所以,我们可以将懒汉式的线程安全写法再进一步改进,称为改进型懒汉式(直接满足线程安全)——通过静态内部类实现
在如上的懒汉单例模式中,对于多线程环境中。可以通过常见的如synchronized等方式实现线程安全,同时,可以通过Java静态内部类的方式实现进一步改进,常见代码如下:
package Paint;
public class SingleTon {
// 利用静态内部类特性实现外部类的单例
private static class SingleTonBuilder {
private static SingleTon singleTon = new SingleTon();
}
// 私有化构造函数
private SingleTon() {
}
public static SingleTon getInstance() {
return SingleTonBuilder.singleTon;
}
public static void main(String[] args) {
SingleTon instance = getInstance();
}
}
主要原理:Java中静态内部类可以访问其外部类的成员属性和方法,同时,静态内部类只有当被调用的时候才开始首次被加载,利用此特性,可以实现懒汉式,在静态内部类中静态初始化外部类的单一实例即可。
-------- 总结 ---------
单例模式将对象的创建和使用分离,在使用对象时无需关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展,它有如下要点:
1)某个类只能有一个实例
2)它必须自行创建这个实例
3)它必须自行向整个系统提供这个实例
单例模式结构图.jpg
从上图中可以看出,单例模式结构图中只包含了一个单例的角色:
1)在单例类的内部实现只生成一个实例,同时它提供一个静态的GetInstance()方法,让客户可以访问它的唯一实例;
2)为了防止在外部对单例类实例化,它的构造函数被设为private;
3)在单例类的内部定义了一个Singleton类型的静态对象,作为提供外部共享的唯一实例。
单例模式目标明确,结构简单,在软件开发中使用频率相当高,其主要优缺点如下:
主要优点-----
1)提供了对唯一实例的受控访问。
2)由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
3)允许可变数目的示例。基于单例模式,开发人员可以进行扩展,使用与控制单例对象相似的方法来获得指定个数的实例对象,既节省系统资源,又解决了单例对象共享过多有损性能的问题。
主要缺点-----
1)单例模式中没有抽象层,因此单例类的扩展有很大的困难。
2)单例类的职责过重,在一定程度上违背了单一职责的原则。因为单例类既提供了业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦合在一起。
3)很多高级面向对象编程语言如C#和Java等都提供了垃圾回收机制,如果实例化的共享对象长时间不被利用,系统则会认为它是垃圾,于是会自动销毁并回收资源,下次利用时又得重新实例化,这将导致共享的单例对象状态的丢失。
所以,单例模式的使用场景,一定要牢牢记住以下两点:
1)系统只需要一个实例对象。例如:系统要求提供一个唯一的序列号生成器或者资源管理器,又或者需要考虑资源消耗太大而只允许创建一个对象。
2)客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。