单例介绍
可以通俗的理解为该类有且只有一个实例;内部实例化对象;外部调用的时候只能调用该实例。它的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例优缺点
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
单例应用
1、资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件等。
2、控制资源的情况下,方便资源之间的互相通信。如线程池等。
单例实现
饿汉式:
优点:线性安全;在类加载的时候就已经实例化了,调用快。
缺点:资源效率低;有可能永远都不会调用到getInstance()方法。
懒汉式:
优点:避免了饿汉式的那种在没有用到的情况下创建事例,资源利用率高,不执行getInstance()就不会被实例,可以执行该类的其他静态方法。
缺点:懒汉式在单个线程中没有问题,但多个线程同事访问的时候就可能同事创建多个实例,而且这多个实例不是同一个对象,虽然后面创建的实例会覆盖先创建的实例,但是还是会存在拿到不同对象的情况。解决这个问题的办法就是加锁synchonized,第一次加载时不够快,多线程使用不必要的同步开销大。
双重锁:
双重锁出现的原因是因为多个线程等待时,最先进去的线程已经实例化了,但是后面的线程并知道,如果不在锁里面再判断一次单例对象,就会创建多个对象,违背了单例模式的设计思想。
public class Analytics {
private volatile static Analytics analytics;
public Analytics (){}
public static Analytics getInstence(){
if (analytics!=null){
return analytics;
}
synchronized (Analytics .class){
if (analytics==null){
analytics= new Analytics ();
}
}
return analytics;
}
}
多唠叨一下volatile关键字,主要是禁止重排序,初始化一个实例(SomeType st = new SomeType())在java字节码中会有4个步骤:1、申请内存空间;2、初始化默认值(区别于构造器方法的初始化);3、执行构造器方法;4、连接引用和实例。这4个步骤后两个有可能会重排序,1234 1243都有可能,造成未初始化完全的对象发布。volatile可以禁止指令重排序,从而避免这个问题。new Analytics ()是一个非原子操作,编译器可能会重排序,而线程B在线程A赋值完时判断instance就不为null了,此时B拿到的将是一个没有初始化完成的半成品。volatile就能解决这个问题。
静态内部类:(推荐)
public class Analytics {
public Analytics(){}
public static class SingletonHelp {
private final static Analytics instance = new Analytics();
}
public static Analytics getInstance(){
return SingletonHelp.instance ;
}
}
优点 :资源利用率高,不执行getInstance()不被实例,可以执行该类其他静态方法
缺点 :第一次加载时反应不够快