单例模式:确保一个类只有一个实例,并提供一个全局访问点。
用到的设计原则:
1、封装变化
2、组合优于集成
3、针对接口变成而不是针对实现
4、为交互对象之间的松耦合设计而努力
5、类应该对扩展开放,对修改关闭
6、依赖抽象,而不是依赖具体类
最简单的单例
class Singleton {
private static Singleton instantce; private Singleton() {} public static Singleton getInstantce() {
if (instantce == null) {
instantce = new Singleton();
} return instantce;
}
}
但是这样在多线程的情况下就会出问题。当两个线程同时第一次获取单例时,此时instantce还是空,会返回两个实例。
处理多线程单例,最简单的多线程单例
class Singleton {
private static Singleton instantce; private Singleton() {} public static synchronized Singleton getInstantce() {
if (instantce == null) {
instantce = new Singleton();
} return instantce;
}
}
只是多加了一个synchronized关键字。但是这样,同步性能会降低。以后每次调用加保护都是一种累赘。但是如果应用程序可以接受这种额外负担,比如实际上调用这个方法的次数非常少,那就什么也别做,这个并不会影响多少性能。但是如果频繁的使用,可以使用下面的方法。
class Singleton {
private static Singleton instantce = new Singleton(); private Singleton() {} public static synchronized Singleton getInstantce() {
return instantce;
}
}
这样JVM在加载这个类的时候就已经创建出来唯一的单例。
或者利用双重检索的方法
class Singleton {
private static volatile Singleton instantce; private Singleton() {} public static synchronized Singleton getInstantce() {
if (instantce == null) {
synchronized (Singleton.class) {
if (instantce == null) {
instantce = new Singleton();
}
}
}
return instantce;
}
}
这里为什么里面还加了一个判断,和volatile这个关键字有关,volitile可以保证一个变量在多个线程间可见性,如果两个线程同时获取一个单例,这时单例为空,第一个线程先判断,锁住Singleton,第二个线程在等待,等待的地方是判断完为空的位置,第一个线程创建完获取到了,这时已经不为空了,如果这个地方不判断还会是在创建一个,由于volitale保证变量的可见性,第二个线程感知到已经实例化完了,就不去创建,直接获取已经创建好的实例就走了。还有双重锁定的方法不能用在jdk1.2之前,现在都1.8了,一般应该不会接触到这个问题。
还要注意一个问题,就是不同的类加载器加载这个类的时候还是会创建出不同的实例。如果程序中有多个类加载器,小心这个问题,解决的办法是自行指定类加载器。
OO设计认为类应该只做一件事,这里单例类不仅负责管理自己的实例,还在应用程序之间充当角色。尽管如此,由类管理自己的实例的做法还是很常见的,这可以让整体设计更简单,不然还需要设计一个类来管理这个单例类,实在有点繁琐。