当前位置: 首页 > news >正文

团购网站建设优化营商环境指什么

团购网站建设,优化营商环境指什么,威海网站建设whhl,怎么开网店无货源店铺创作内容丰富的干货文章很费心力,感谢点过此文章的读者,点一个关注鼓励一下作者,激励他分享更多的精彩好文,谢谢大家! 双重锁定检查(Double Checked Locking,下称 DCL)是并发下实现懒…

创作内容丰富的干货文章很费心力,感谢点过此文章的读者,点一个关注鼓励一下作者,激励他分享更多的精彩好文,谢谢大家!


双重锁定检查(Double Checked Locking,下称 DCL)是并发下实现懒加载的一个模式,在实现单例模式时很常见,但是要正确实现 DCL,其中涉及到的细节和知识是非常琐碎的,我们这里按照 The "Double-Checked Locking is Broken" Declaration 文章的脉络,结合前几章学习的知识,尝试理解这些知识点。

(这章属于“骚操作”的内容。)

初次尝试

上节中说过 Lazy Initialization,我们的目标是在获取某个实例时只初始化一次,在单线程语境中,我们会这么实现:

class Foo {private Helper helper = null;public Helper getHelper() {if (helper == null)helper = new Helper();return helper;}// other functions and members...
}

但是我们知道这个版本在多线程下是有问题的,因为对 helper 和检查和赋值不是原子的,有可能多个线程同时满足了 if (helper == null) 的判断,最终多个线程都执行了 helper = new Helper 的操作。一个简单的方法是加锁:

class Foo {private Helper helper = null;public synchronized Helper getHelper() {if (helper == null)helper = new Helper();return helper;}// other functions and members...
}

注意代码里的 synchronized。这个代码能正确运行,但是效率低下,因为 synchronized 是互斥锁,后续所有 getHelper 调用都得加锁。于是我们希望在 helper 正确初始化后就不再加锁了,尝试如下实现:

class Foo {private Helper helper = null;public synchronized Helper getHelper() {if (helper == null)             // ① 第一次检查synchronized(this) {        // ② 对 helper 加锁if (helper == null)         // ③ 同上个实现helper = new Helper();}return helper;}// other functions and members...
}

代码的初衷是:

  1. 如果正确初始化后,所有的 getHelper ① 的条件失败,于是不需要synchronized
  2. 如果未被正确初始化,则同上个实现一样,加锁进行初始化。

Unfortunately, that code just does not work in the presence of either optimizing compilers or shared memory multiprocessors.

很可惜,这段代码在编译器优化或多核的环境下是“错误”的。在这章中,我们会尝试去理解为什么它不正确,及为什么一些 bugfix 后依旧不正确。丑话说在前:

There is no way to make it work without requiring each thread that accesses the helper object to perform synchronization.

用人话来说,就是如果不把 helper 对象设置成 volatile 的,这段代码就不可能正确。

指令重排

第一个可能的问题是重排序1。这行代码 helper = new Helper(); 看上去是原子,从字节码的角度可以理解成下面几个步骤:

instance = Helper.class.newInstance(); // 1. 分配内存
Helper::constructor(instance);         // 2. 调用构造函数初始化对象
helper = instance;                     // 3. 让 helper 指向新的对象

前面章节说过,JVM 可能会对指令做重排序,所做的保证是不影响“单线程”的执行结果,那么可能排序成这样:

instance = Helper.class.newInstance(); // 1. 分配内存
helper = instance;                     // 3. 让 helper 指向新的对象
Helper::constructor(instance);         // 3. 调用构造函数初始化对象

那么在 #3 执行之前,helper 指向的内存地址未被初始化,是不安全的。在多线程下,可能会变成:

--------------- Thread A -------------------+--------------- Thread B --------------
if (helper == null)                         |synchronized(this) {                      |if (helper == null) {                   |instance = Helper.class.newInstance();|helper = instance;                    || if (helper == null) // false| return helper| // ... do something with helper.Helper::constructor(instance);        |}                                       |}                                         |
return helper;                              |

即由于重排,helper 指针已经有值了,但是还未初始化,导致此时线程 B 拿着未初始化的 helper 做了其它的操作,这是有风险的。

注意的是,即使编译器不做重排序,CPU 和缓存也可能会做重排序。

试图挽救重排序

上面的问题,我们根本目标是要保证 synchronized 块结束时(初始化完成后),相应的值才被其它线程看到,于是我们可以用下面这个 trick:

class Foo {private Helper helper = null;public Helper getHelper() {if (helper == null) {Helper h;                     // ① 创建了临时变量synchronized(this) {h = helper;                 // ② 保证读取最新的 helper 值if (h == null)synchronized (this) {   // ③ 尝试用内部锁解决重排序h = new Helper();     // ④ 创建新的实例}                       // ⑤ 释放了内部的锁helper = h;                 // ⑥ 将新的实例赋值给 helper}}return helper;}// other functions and members...
}

这里的想法是想通过 ③ 处的锁来阻止重排序,更准确地说,是希望在 ⑤ 释放锁的地方能提供内存屏障(memory barrier),从而保证 h = new Helper 一定在 helper = h 之前执行。

很可惜这个“希望”现实中不成立。Happens Before 里规定的是:

监视器上的 unlock 操作 Happens Before 同一个监视器的 lock 操作

换言之,为了保证 unlock Happens Before 其它的 lock 操作,JVM 需要保证在锁释放时,synchronized 块之前的操作都已经完成并写回到内存里。但是这个规则并没有说 synchronized 块之后的操作不能重排序到synchronized 块之前执行。因此上面这种修改的“美好希望”实际上并不成立2。

此路不通

即使我们真的能保证 helper 在被赋值之前就已经正确初始化了3,这种方式就能正确工作了吗?不能。

问题不仅仅在于写的一方,即使 helper 被正确初始化并赋值,由于另一个线程所在的 CPU 可能会从缓存中读取 helper 的值,如果 helper 的新值还没有被更新到缓存中,则读取的值可能还是 null

等等!不是说 synchronized 会保证可见性吗?是的,但它保证的是 unlock 操作前的更新对同一个监视器的 lock 操作可见,但现在另一个线程根本没有进入 synchronized 代码块,此时 JVM 不保证可见。

volatile

经过前面的分析,想起了前面章节提到的 volatile 关键字(JDK 1.5 后支持)有这么一条 Happens Before 规则:

volatile 变量规则:写入 volatile 变量 Happens Before 读取该变量

它可以提供额外的可见性保证。于是我们可以这么(正确)实现:

class Foo {private volatile Helper helper = null; // 注意变量声明了 volatilepublic Helper getHelper() {if (helper == null) {synchronized(this) {if (helper == null)helper = new Helper();}}return helper;}
}

这个实现里,写入 helper 之前的操作,如 Helper 对象的初始化,在 helper 被读取(如判断 helper == null)必须可见。换句话说,前文讨论的两种情况:重排序与可见性问题都由于 volatile 的语义得到保证。

那么 volatile 是不是会降低性能?《Java 并发编程实战》第三章的注解里说

在当前大多数处理器架构上,读取 volatile 变量的开销只比读取非 volatile 变量的开销略高一点

几个例外

例外不是说 volatile 方式的正确性有例外,而是对于一些特殊情形,有特殊的解法。

static 单例

对于是 static 的单例,最好的初始化方式是利用 Java 类加载机制,如下:

 
public class Foo {private static class Holder {private static Helper helper = new Helper();}public static Helper getInstance() {return Holder.helper;}
}

32 位 primitive

这里的知识点是 32 位的 primitive 类型变量的读写是原子的。如果初始化的方法是幂等的,则可以这么实现:

 
class Foo {private int cachedHashCode = 0;public int hashCode() {int h = cachedHashCode;if (h == 0)synchronized(this) {if (cachedHashCode != 0) return cachedHashCode;h = computeHashCode();cachedHashCode = h;}return h;}// other functions and members...
}

当然,如果方法是幂等的,甚至都不需要同步:

 
class Foo {private int cachedHashCode = 0;public int hashCode() {int h = cachedHashCode;if (h == 0) {h = computeHashCode();cachedHashCode = h;}return h;}// other functions and members...
}

为什么一定需要 32 位呢?因为 64 位的操作不是原子的,于是可能造成前后 32 位不是一起写入内存的,而另一个线程只读取先写入的 32 位,读到的结果不正确。

final

如果前文的 Helper 类是不可变的(immutable),具体地说,Helper 的所有属性都是 final 的,那么即使不加 volatile,DCL 也是正确的。这是因为 JVM 对 final 关键字有一些特殊的语义,有兴趣的可以参考 JSL 第 17 章

小结

本章中我们讲解了 The "Double-Checked Locking is Broken" Declaration 文章中关于 DCL 的各个示例,并结合前面章节中学到的 Happens Before 关系的知识去理解 DCL 成立或不成立的原因。

有时候我们会认为:写的时候加锁就行了,读操作不需要加锁。本节的例子就说明了这种观点不成立,会有可见性和顺序性的问题。最简单的解决方式是读操作也加锁,如果性能达不到要求,也可以像本节一样使用 volatile,但我个人不建议这么用,因为有太多细节需要考虑,可以使用 JUC 中的 ReadWriteLock 来加读写锁。

可以看到,要正确地实现并发程序,难度是很大的,并且要了解很多细节。当然也不必灰心,已经有前人为我们辅好了路,日常工作中我们只需要跟随前人的脚步,就可以满足绝大多数需求。

http://www.ho-use.cn/article/1455.html

相关文章:

  • 网站运营适合什么样的人做本地推荐本地推荐
  • 网站建设网页制作企业营销策划
  • 南昌网站建设专业公司采集站seo提高收录
  • 医院网站建设步骤百度快速提交入口
  • 微信h5免费制作网站必应搜索推广
  • 智慧团建官网登录口广州seo全网营销
  • 网站用的字体如何做谷歌seo推广
  • 无锡惠山区建设局网站搜索引擎整合营销
  • 有没有做网站的制作网站免费
  • 顺义手机网站设计网址制作
  • 网站建设服务价格表百度seo排名优化排行
  • 小区网站开发论文最经典最常用的网站推广方式
  • 什么视频网站可以做链接地址app投放推广
  • 环保主题网站模板廊坊seo整站优化
  • 怎么做恶搞网站目录搜索引擎有哪些
  • 公众号怎么做网站模板网站
  • 专门做广东11选5的网站电话营销系统
  • 做票据业务的p2p网站今日时政新闻热点
  • 企业网站网络推广怎么推广平台
  • 交友网站建设策划方案(2)软考培训机构哪家好一点
  • 检察院内部网站升级建设百度公司官网招聘
  • 受欢迎的南昌网站建设域名解析ip地址查询
  • 什么样的企业需要做网站网站建设步骤流程详细介绍
  • 给网站增加功能怎么做东莞公司网上推广
  • wordpress文章链接怎么改seo课程培训学校
  • 做的最好的视频教学网站刷赞网站推广永久
  • 出名的网站制作正规公司2345网址导航怎么下载
  • 百度云加速 wordpress南昌seo营销
  • 网络营销实现方式有哪些天机seo
  • 网站怎么利用朋友圈做推广关键词整站优化