1. 什么是单例模式?
《Head First设计模式》书中定义,单例模式是确保一个类最多只有一个实例,并提供全局访问点。在java中实现单例模式需要一个私有构造函数、一个静态变量和一个静态方法(枚举法实现单例模式例外)。使用单例模式必须要时刻注意并解决线程安全问题。
自己理解,单例模式中,类负责创建自己的对象,同时确保只有一个对象被创建。这个类提供了访问自己这个唯一对象的方式,可以直接访问,不需要实例化。
2. 优点
(1)对于系统中反复使用的对象,可以一次创建多次使用,减少了反复创建对象的系统开销
(2)由于系统中只存在一个对象,可以避免对共享资源的多重占用,可以节约系统内存资源。
3. 缺点
(1)单例模式没有抽象层,不能继承进行扩展(单例模式的构造器是私有的,除了自己其他类不能访问,因此不能够继承)。
(2)单例类的职责过重,除了要维护自己对象的创建,还需要干一下其他的事情,违背了“单一职责原则”。
4. 使用场景
(1)需要频繁实例化然后销毁的对象。
(2)创建对象耗时又耗资源,而且系统使用此对象又比较频繁,比如IO操作也数据库连接等。
(3)有状态的工具类。
(4)应用实例:系统外部资源对象(打印机)、系统内部配置文件对象、web系统计数器、日志应用、多线程线程池设计、数据库连接池设计、HttpApplication是单例的典型应用。
5. 单例模式的5中实现方式
5.1 懒汉模式
/**
* 单例模式-懒汉模式
*/
public class LazySingleton {
private static LazySingletonlazySingleton=null;
private LazySingleton() {
System.out.println("LazySingleton has instanced!");
}
/**
* 同步方法,保证只会有一个线程进入此方法执行程序,确保了线程安全问题
* 只要是有线程调用getInstance方法,方法都会同步,
* 但实际上只需要第一次调用的时候同步(解决办法为双重校验锁式),
* 因此对系统效率造成很大的问题
*/
public static synchronized LazySingleton getInstance(){
if(lazySingleton==null){
lazySingleton=new LazySingleton();
}
return lazySingleton;
}
}
5.2 双重检验锁
/**
* 单例模式-双重检验锁
*/
public class DoubleCheckLockSingleton {
private volatile static DoubleCheckLockSingleton dclSingleton =null;
private DoubleCheckLockSingleton() {
System.out.println("DoubleCheckLockSingleton has instanced!");
}
/**
* 在JDK1.5之后,双重检查锁定才能够正常达到单例效果。为什么叫双重检验锁,
* 因为有两次检查 dclSingleton == null,一次是在同步块外,一次是在同步块内。
* 为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,
* 如果在同步块内不进行二次检验的话就会生成多个实例了。
*/
public static DoubleCheckLockSingleton getInstance() {
if (dclSingleton ==null) {
synchronized (DoubleCheckLockSingleton.class) {
if (dclSingleton ==null) {
dclSingleton = new DoubleCheckLockSingleton();
}
}
}
return dclSingleton;
}
}
注意使用关键字volatile,dclSingleton =new DoubleCheckLockSingleton();
这个操作并非是原子性的,在JVM中主要干了下面三个事情:
a. 给dclSingleton分配内存;
b. 调用DoubleCheckLockSingleton的构造函数来初始化对象;
c. 将dclSingleton对象指向分配的内存空间(执行完这步dclSingleton就为非null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是1-2-3 也可能是1-3-2。如果是后者,则在3 执行完毕、2未执行之前,被线程二抢占了,这时 dclSingleton已经是非null 了(但却没有初始化),所以线程二会直接返回dclSingleton,然后使用,然后顺理成章地报错。
有些人认为使用 volatile 的原因是可见性,也就是可以保证线程在本地不会存有 instance 的副本,每次都是去主内存中读取。但其实是不对的。使用 volatile的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完1-2-3 之后或者1-3-2 之后,不存在执行到1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。
但是特别注意在 Java 5 以前的版本使用了 volatile 的双检锁还是有问题的。其原因是Java 5 以前的JMM (Java内存模型)是存在缺陷的,即时将变量声明成 volatile也不能完全避免重排序,主要是 volatile变量前后的代码仍然存在重排序问题。这个 volatile屏蔽重排序的问题在 Java 5中才得以修复,所以在这之后才可以放心使用 volatile。
5.3 饥汉模式
/**
* 单例模式-饥汉式
*/
public class HungrySingleton {
/*在类初始化的时候就进行了实例化,因此不存在线程安全问题*/
private static HungrySingletonhungrySingleton=new HungrySingleton();
private HungrySingleton() {
System.out.println("HungrySingleton has instanced!");
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
饥汉模式不是一种延迟加载模式,是当系统加载类的时候就进行了实例化,不管有没有调用getInstance方法,若永远没有调用getInstance方法,实例化的对象就没有被使用,造成系统资源浪费。
5.4 静态内部类
/**
* 单例模式-静态内部类式
*/
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
System.out.println("StaticInnerClassSingleton has instanced!");
}
private static class InnerClass {
private static StaticInnerClassSingletonsicSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.sicSingleton;
}
}
静态内部类在外部类加载的时候是不会加载的,只有当使用的时候才会加载,因为是私有的,所有只能通过getInstance方法才能调用,而static关键字又保证了sicSingleton实例对象只会被实例化一次,因此这种方法是懒汉式的,不会出现线程安全问题也不依赖JDK版本,是比较好的一种方式。
5.5 枚举(Enum)
/**
* 单例模式-枚举式
*/
public enum EnumSingleton {
INSTANCE;
private Resource resource;
private EnumSingleton(){
resource=new Resource();
}
public Resource getInstance(){
return resource;
}
public static void main(String[]args) {
Resource r=EnumSingleton.INSTANCE.getInstance();
Resource r2=EnumSingleton.INSTANCE.getInstance();
System.out.println(r.equals(r2));
}
}
/**
* 需要使用单例模式的资源
*/
class Resource{
Resource(){
System.out.println("Resource has instanced!");
}
}
结果为:
Resource has instanced!
True
枚举默认是线程安全的,是从java5才出现的,而且还能防止反序列化导致的对象复制。但是使用的比较少,是《effective java》推荐实现单例模式的一种方法。
参考资料http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
【四川乐山程序员联盟,欢迎大家加群相互交流学习5 7 1 8 1 4 7 4 3】