1.什么是单例模式
Java中单例模式定义“一个类有且仅有一个实例并且自行实例化向整个系统提供。” 单例模式就是一个类只有一个对象的实例
2.为什么要用单例模式
某些应用场景中有些对象有且只能有一个如果创建多个就会出现很多问题。例如一个操作系统中只能打开一个任务管理器如果可以同时打开多个任务管理器就会出现问题。
3.怎么创建单例模式
#####方法一饿汉模式
public class Singleton{// 1.将构造方法私有化不允许外部直接创建对象只能通过类名调用静态方法来获取对象private Singleton(){}//2.创建类的唯一实例使用private static修饰private static final Singleton instance new Singleton();//3.提供一个用于获取实例的静态方法使用public static修饰public static Singleton getInstance(){return instance;}}
创建思路 1如何保证只有一个实例 方法将构造方法设置为私有的这样外部类就无法直接创建类的对象 那么问题来了…… 2外部类是需要一个唯一实例的无法创建怎么办 方法在类的内部创建类的唯一实例并声明为static(声明为static的目的是将实例声明为类的成员这样在其他类中就可以通过类名直接访问这个实例)同时声明为private防止外部直接访问这个实例但是这样就会导致使用类名调用这个实例不能实现此时需要创建一个方法来调用这个实例 3提供一个用于获取实例的方法要用static修饰类方法
#####方法二懒汉模式
public class Singleton{// 1.将构造方法私有化不允许外部直接创建对象private Singleton(){}//2.**声明**类的唯一实例使用private static修饰只是声明类的实例并没有实例化private static final Singleton instance;//3.提供一个用于获取实例的静态方法使用public static修饰public static Singleton getInstance(){if(instancenull){instancenew Singleton();}return instance;}
创建思路 1同懒汉模式 2声明类的唯一实例只是声明创建工作留在方法中 3提供一个获取实例的方法方法中创建实例
##4.饿汉模式和懒汉模式的区别 ## 饿汉模式的特点是加载类比较慢因为加载类的时候就已经创建了对象但是运行时获取对象的速度较快 线程安全 懒汉模式的特点是加载类比较快加载类的时候没有创建对象但是运行时获取对象的速度较慢运行时创建对象 非线程安全两个线程同时校验instance非空们就会同时创建instance对象
##5.改进版双重锁 ##
public class Singleton{private static volatile Singleton instancenull;//volatile关键字防止jvm优化代码private Singleton(){}public static Singleton getInstance(){if(instancenull){synchronized(Singleton.class){if(instancenull){instancenew Singleton();}}}return instance;}}
接着上一篇的单例模式说起为什么要在多线程中创建单例模式的时候要进行双重锁定先回顾一下双重锁定的代码块。
public class SingleTon {
private static SingleTon singleTon null;public SingleTon() {// TODO Auto-generated constructor stub}public static SingleTon getInstance(){if (singleTon null) {synchronized (SingleTon.class) {if (singleTon null) {singleTon new SingleTon();}}}return singleTon;}
} 为何要使用双重检查锁定呢 考虑这样一种情况就是有两个线程同时到达即同时调用 getInstance() 方法 此时由于 singleTon null 所以很明显两个线程都可以通过第一重的 singleTon null 进入第一重 if 语句后由于存在锁机制所以会有一个线程进入 lock 语句并进入第二重 singleTon null 而另外的一个线程则会在 lock 语句的外面等待。 而当第一个线程执行完 new SingleTon语句后便会退出锁定区域此时第二个线程便可以进入 lock 语句块 此时如果没有第二重 singleTon null 的话那么第二个线程还是可以调用 new SingleTon 语句 这样第二个线程也会创建一个 SingleTon实例这样也还是违背了单例模式的初衷的 所以这里必须要使用双重检查锁定。 细心的朋友一定会发现如果我去掉第一重 singleton null 程序还是可以在多线程下完好的运行的 考虑在没有第一重 singleton null 的情况下 当有两个线程同时到达此时由于 lock 机制的存在第一个线程会进入 lock 语句块并且可以顺利执行 new SingleTon 当第一个线程退出 lock 语句块时 singleTon 这个静态变量已不为 null 了所以当第二个线程进入 lock 时 还是会被第二重 singleton null 挡在外面而无法执行 new Singleton 所以在没有第一重 singleton null 的情况下也是可以实现单例模式的那么为什么需要第一重 singleton null 呢 这里就涉及一个性能问题了因为对于单例模式的话new SingleTon只需要执行一次就 OK 了 而如果没有第一重 singleTon null 的话每一次有线程进入 getInstance时均会执行锁定操作来实现线程同步 这是非常耗费性能的而如果我加上第一重 singleTon null 的话 那么就只有在第一次也就是 singleTton null 成立时的情况下执行一次锁定以实现线程同步 而以后的话便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了这样就可以解决由线程同步带来的性能问题了。 为什么要加volatile 首先要理解new Singleton()做了什么。 new一个对象有几个步骤。 1.看class对象是否加载如果没有就先加载class对象 2.分配内存空间初始化实例 3.调用构造函数 4.返回地址给引用。 而cpu为了优化程序可能会进行指令重排序打乱这34这几个步骤导致实例内存还没分配就被使用了,进而返回null。 双重锁模式的优点 //这个模式将同步内容放到if内部提高了执行的效率不必每次获取对象时都进行同步只有第一次才同步创建了以后就没必要了。 //这种模式中双重判断加同步的方式比第一个例子中的效率大大提升因为如果单层if判断在服务器允许的情况下 //假设有一百个线程耗费的时间为100*同步判断时间if判断时间而如果双重if判断100的线程可以同时if判断理论消耗的时间只有一个if判断的时间。 //所以如果面对高并发的情况而且采用的是懒汉模式最好的选择就是双重判断加同步的方式。