建设工程竣工备案网站,网站开发公司的销售方式,公司网站开发设计,一个网站绑定2个域名单例模式之「双重校验锁」
单例模式
单例即单实例#xff0c;只实例出来一个对象。一般在创建一些管理器类、工具类的时候#xff0c;需要用到单例模式#xff0c;比如JDBCUtil 类#xff0c;我们只需要一个实例即可#xff08;多个实例也可以实现功能#xff0c;但是增…单例模式之「双重校验锁」
单例模式
单例即单实例只实例出来一个对象。一般在创建一些管理器类、工具类的时候需要用到单例模式比如JDBCUtil 类我们只需要一个实例即可多个实例也可以实现功能但是增加了代码量且降低了性能。
如何实现单例
将构造方法私有化提供一个全局唯一获取该类实例的方法帮助用户获取类的实例
应用场景
主要被用于一个全局类的对象在多个地方被使用并且对象的状态是全局变化的场景下。
单例模式的优点
单例模式为系统资源的优化提供了很好的思路频繁创建和销毁对象都会增加系统的资源消耗而单例模式保障了整个系统只有一个对象能被使用很好地节约了资源。
单例模式的写法
饿汉模式懒汉模式静态内部类双重校验锁
饿汉模式 顾名思义饿汉模式就是加载类的时候直接new一个对象后面直接用即可。 饿汉模式指在类中直接定义全局的静态对象的实例并初始化然后提供一个方法获取该实例对象。 代码如下 public class Singleton {// 使用static修饰类加载的时候new一个对象private static Singleton INSTANCE new Singleton();// 构造器私有化private Singleton() {}public static Singleton getInstance() {return INSTANCE;}
}懒汉模式 顾名思义懒汉模式就是加载类的时候只声明变量不new对象后面用到的时候再new对象然后把对象赋给该变量。 定义一个私有的静态对象INSTANCE之所以定义INSTANCE为静态是因为静态属性或方法是属于类的能够很好地保障单例对象的唯一性 然后定义一个静态方法获取该对象如果对象为null则 new 一个对象并将其赋值给INSTANCE。 代码如下 public class Singleton {private static Singleton INSTANCE;// 构造器私有化private Singleton() {}public static Singleton getInstance() {if (INSTANCE null) {INSTANCE new Singleton();}return INSTANCE;}
}饿汉模式和懒汉模式的区别在于
饿汉模式是在类加载时将其实例化的在饿汉模式下在Class Loader完成后该类的实例便已经存在于JVM中了即在getInstance方法第一次被调用前该实例已经存在了new对象的操作不在getInstance方法内。而懒汉模式在类中只是定义了变量但是并未实例化实例化的过程是在获取单例对象的方法中实现的即在getInstance方法第一次被调用后该实例才会被创建new对象的操作在getInstance方法内。此外注意饿汉模式的实例在类加载的时候已经存在于JVM中了因此是线程安全的懒汉模式通过第一次调用getInstance才实例化该方法不是线程安全的后面讲怎么优化
静态内部类 静态内部类通过在类中定义一个静态内部类将对象实例的定义和初始化放在内部类中完成我们在获取对象时要通过静态内部类调用其单例对象。 之所以这样设计是因为类的静态内部类在JVM中是唯一的这很好地保障了单例对象的唯一性。 静态内部类的单例实现方式同样是线程安全的。 代码如下 public class Singleton {private static class SingletonHolder {private static final Singleton INSTANCE new Singleton();}private Singleton(){}public static final Singleton getInstance(){return SingletonHolder.INSTANCE;}
}饿汉模式和静态内部类实现单例模式的优点是写法简单缺点是不适合复杂对象的创建。对于涉及复杂对象创建的单例模式比较优雅的实现方式是懒汉模式但是懒汉模式是非线程安全的下面就讲一下懒汉模式的升级版——双重构校验锁模式双重构校验锁是线程安全的。
双重构校验锁
饿汉模式是不需要加锁来保证单例的而懒汉模式虽然节省了内存但是却需要使用锁来保证单例因此双重校验锁就是懒汉模式的升级版本。
单线程懒汉模式实现 普通的懒汉模式在单线程场景下是线程安全的但在多线程场景下是非线程安全的。 先来看看普通的懒汉模式实现 public class Singleton {private static Singleton INSTANCE;private Singleton() {}public static Singleton getInstance() {if (INSTANCE null) {INSTANCE new Singleton();}return INSTANCE;}
}单线程懒汉模式的问题 上面这段代码在单线程环境下没有问题但是在多线程的情况下会产生线程安全问题。 在多个线程同时调用getInstance方法时由于方法没有加锁可能会出现以下情况 ① 这些线程可能会创建多个对象 ② 某个线程可能会得到一个未完全初始化的对象 为什么会出现以上问题 对于 ① 的情况解释如下 public static Singleton getInstance() {if (INSTANCE null) {/*** 由于没有加锁当线程A刚执行完if判断INSTANCE为null后还没来得及执行INSTANCE new Singleton()* 此时线程B进来if判断后INSTANCE为null且执行完INSTANCE new Singleton()* 然后线程A接着执行由于之前if判断INSTANCE为null于是执行INSTANCE new Singleton()重复创建了对象*/INSTANCE new Singleton();}return INSTANCE;
}对于 ② 的情况解释如下 public static Singleton getInstance() {if (INSTANCE null) {/*** 由于没有加锁当线程A刚执行完if判断INSTANCE为null后开始执行 INSTANCE new Singleton()* 但是注意new Singleton()这个操作在JVM层面不是一个原子操作**具体由三步组成1.为INSTANCE分配内存空间2.初始化INSTANCE3.将INSTANCE指向分配的内存空间* 且这三步在JVM层面有可能发生指令重排导致实际执行顺序可能为1-3-2** 因为new操作不是原子化操作因此可能会出现线程A执行new Singleton()时发生指令重排的情况* 导致实际执行顺序变为1-3-2当执行完1-3还没来及执行2时虽然还没执行2但是对象的引用已经有了* 只不过引用的是一个还没初始化的对象此时线程B进来进行if判断后INSTANCE不为null* 然后直接把线程A new到一半的对象返回了*/INSTANCE new Singleton();}return INSTANCE;
}解决问题加锁 为了解决问题 ①我们可以对 getInstance() 这个方法加锁。 public class Singleton {private static Singleton INSTANCE;private Singleton() {}public static synchronized Singleton getInstance() { // 加锁if (INSTANCE null) {INSTANCE new Singleton();}return INSTANCE;}
}仔细看这里是粗暴地对整个 getInstance() 方法加锁这样做代价很大因为只有当第一次调用 getInstance() 时才需要同步创建对象创建之后再次调用 getInstance() 时就只是简单的返回成员变量而这里是无需同步的所以没必要对整个方法加锁。 由于同步一个方法会降低上百倍甚至更高的性能 每次调用获取和释放锁的开销似乎是可以避免的一旦初始化完成获取和释放锁就显得很不必要。 所以可以只对方法的部分代码加锁 public class Lock2Singleton {private static Lock2Singleton INSTANCE;private Lock2Singleton() {}public static Lock2Singleton getSingleton() {// 因为INSTANCE是静态变量所以给Lock2Singleton的Claa对象上锁synchronized(Lock2Singleton.class) { // 加 synchronizedif (INSTANCE null) {INSTANCE new Lock2Singleton();}}return INSTANCE;}
}优化后的代码选择了对 if (INSTANCE null) 和 INSTANCE new Lock2Singleton()加锁 这样每个线程进到这个方法中之后先加锁这样就保证了 if (INSTANCE null) 和 INSTANCE new Lock2Singleton() 这两行代码被同一个线程执行时不会有另外一个线程进来由此保证了创建的对象是唯一的。 对象的唯一性保证了也就是解决了问题①但是如何解决问题②呢虽然加了 synchronized但是 synchronized 是不能禁止指令重排的也就是说INSTANCE new Lock2Singleton(); 这行代码在 JVM 层面还是有可能发生 1-3-2 的现象那要怎么保证绝对的1-2-3顺序呢也就是禁止指令重排序答案是加 volatile 关键字。 public class Lock2Singleton {private volatile static Lock2Singleton INSTANCE; // 加 volatileprivate Lock2Singleton() {}public static Lock2Singleton getSingleton() {synchronized(Lock2Singleton.class) { // 加 synchronizedif (INSTANCE null) {INSTANCE new Lock2Singleton();}}return INSTANCE;}
}这样总可以解决问题 ① 和 ② 了吧然而你以为这就结束了吗NO这段代码从功能层面来讲确实是已经结束了但是性能方面呢是不是还有可以优化的地方 答案是有 值得优化的地方就在于 synchronized 代码块这里。每个线程进来不管三七二十一都要先进入同步代码块再说如果说现在 INSTANCE 已经不为null了那么此时当一个线程进来先获得锁然后才会执行 if 判断。我们知道加锁是非常影响效率的所以如果 INSTANCE 已经不为null是不是就可以先判断再进入 synchronized 代码块。如下 public class Lock2Singleton {private volatile static Lock2Singleton INSTANCE; // 加 volatileprivate Lock2Singleton() {}public static Lock2Singleton getSingleton() {if (INSTANCE null) { // 双重校验第一次校验synchronized(Lock2Singleton.class) { // 加 synchronizedif (INSTANCE null) { // 双重校验第二次校验INSTANCE new Lock2Singleton();}}}return INSTANCE;}
}在 synchronized 代码块之外再加一个 if 判断这样当 INSTANCE 已经存在时线程先判断不为null然后直接返回避免了进入 synchronized 同步代码块。 那么可能又有人问好了我明白了在 synchronized 代码块外加一个 if 判断是不是就意味着里面的那个 if 判断可以去掉 当然不可以 如果把里面的 if 判断去掉就相当于只对 INSTANCE new Lock2Singleton() 这一行代码加了个锁只对一行代码加锁那你岂不是加了个寂寞加锁的目的就是防止在第二个if判断和new操作之间有别的线程进来结果还是会引起问题①。 所以两次校验一次都不能少
总结 最终单例模式双重校验锁模式的完整代码实现如下 public class Lock2Singleton {private volatile static Lock2Singleton INSTANCE; // 加 volatileprivate Lock2Singleton() {}public static Lock2Singleton getSingleton() {if (INSTANCE null) { // 双重校验第一次校验synchronized(Lock2Singleton.class) { // 加 synchronizedif (INSTANCE null) { // 双重校验第二次校验INSTANCE new Lock2Singleton();}}}return INSTANCE;}
}过程如下 判断 INSTANCE 是否为null检查变量是否被初始化不去获得锁如果已被初始化立即返回这个变量 不为null直接返回不用去竞争锁 为null获取锁然后再次判断虽然已经判断过但是在第一个if和synchronized之间仍有可能被另外线程插入导致第一个if判断为null时当进入同步代码块之后再次判断时已经不为null了所以需要再次判断 是否为null 为null创建并返回 不为null直接直接返回 为什么是双重校验 第二次校验是为了解决问题①即避免多个线程重复创建对象。 第一次校验是为了提高效率避免 INSTANCE 不为null时仍然去竞争锁。 为什么加 volatile 加 volatile 是为了禁止指令重排序也就是为了解决问题②即避免某个线程获取到其他线程没有初始化完全的对象。
-----------------------------------------------------------------------------------
offer突击训练营简介
1针对不知道怎么面试面试没有信心的小伙伴我们会给你一个offer保障。
2我们会监督你15-20天内把面试体系技术点掌握至少7成这样足够你去找到满意的工作了。
3我们是面向面试学习指导不会带你们去写代码会把项目真实开发的迭代过程和技术细节如何实现业务功能都详细教清楚你能在面试中流畅表达清楚就行了项目经验你不用担心(技术老师提供的真实项目经验肯定拿的出手)自己学和别人带着系统学效率完全不一样。
详情请点击这里offer突击训练营给你一个offer的保障求职跳槽的看过来