前言

高效并发是程序员们写代码时一直所追求的,HotSpot虚拟机开发团队也为此付出了很多努力,为了在线程之间更高效地共享数据,以及解决竞争问题,HotSpot开发团队做出了各种锁的优化技术常见的有:自适应自旋锁(Adaptive Spinning)、锁消除(Lock Elimination)、锁粗化(Lock Coarsening)、轻量级锁(Lightweight Locking)和偏向锁(Biased Locking)等。

自旋锁与自适应自旋

互斥同步对性能最大的影响是阻塞的实现,线程的挂起和恢复的操作时要消耗系统资源的,在并发时频繁的挂起和恢复线程这会给系统带来很大的压力。在许多应用中共享数据的锁定状态只会持续很短的一段时间,这段时间可能比线程的挂起和恢复的时间还短,这样切换线程的状态是很不值得的。因此虚拟机开发团队在JDK1.4.2中引入了自旋锁,在并发执行一段代码时,如果已经有线程获得锁,后面的线程不会被直接挂起,而是区执行一个空循环(自旋),在若干个空循环后,线程如果获得了锁,则继续执行,若线程依然不能获得锁,才会被挂起。自旋次数默认是10次,可以使用-XX:PreBlockSpin来更改。

JDK1.6中引入了自适应锁,意味着自旋的时间不再固定,而是有之前的自旋时间及锁的拥有者状态来决定,若上一次成功获得锁,那么这一次允许自旋更长时间,若这个线程很少获得锁,有可能就跳过自旋直接被挂起。

锁消除

锁消除指虚拟机在即时编译时,通过对运行上下文的扫描,发现一些被要求同步的代码,不可能存在共享数据竞争的锁,这个时候就需要把这些锁进行消除,这样可以节省毫无意义的请求时间。很多时候同步措施并不是开发人员手动加上的,而是JVM在运行期间转换时加上的。

如下代码:

public String concatString(String str1,String str2,String str3){
return str1+str2+str3;
}

因为String类是不可变的,每次的连接操作都是生成新的字符串,在JDK1.5之前会转换成StringBuffer对象的连续append()操作,在JDK1.5及以后的版本中会转换成Stringbuilder对象的连续append()操作。

转换后的代码如下:

public String concatString(String str1,String str2,String str3){
StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(str1);
stringBuilder.append(str2);
stringBuilder.append(str3); return stringBuilder.toString();
}

每一个append()方法都有一个同步块,锁的是stringBuilder对象,但是stringBuilder对象是concatString()方法的局部变量,显然不会被其他线程访问,因此可以安全的消除这里锁。

锁粗化

在编码时推荐同步块的作用范围尽量的小,这样范围小了,出现竞争时等待线程也能最快的拿到锁,但是如果频繁的加锁和解锁也是很消耗资源的,所以虚拟机开发团队对这种情况下的锁进行了粗化,就是说如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁的同步范围粗化到整个操作序列的外部。例如上面的代码中stringBuilder对象的每一个append()方法都有一个锁,虚拟机会把锁范围扩展到第一个append()操作之前直到最后一个append()操作之后。

轻量级锁

轻量级锁

轻量级锁是相对于操作系统互斥量的传统“重量”锁来说的。并不是来代替重量级锁,而是指在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

理解轻量级锁要先理解对象肉的内存布局,对象头分为两部分:

第一部分

用于存储对象自身的运行时数据,哈希码、GC分代年龄等。这部分数据的长度在32位和64位的虚拟分别为32bit和64bit,官方称它为“Mark Word”,它是实现轻量级锁和偏向锁的关键。

第二部分

用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用户存储数组长度。

对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的信息,它会根据对象的状态复用自己的存储空间。

轻量级锁加锁过程

  1. 在代码进入同步块的时候,如果此时同步对象没有被锁定(锁定标志位“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用户存储锁对象目前的Mark Word的拷贝。
  2. 然后虚拟机将使用CAS(Compare-And-Swap)操作尝试将对象的Mark Word更新为指向Lock Record的指针。
  3. 如果这个更新操作成功了,那么这个线程就拥有了该对象的锁,并且Mark Word的锁标志变为“00”。
  4. 如果这个更新操作失败了,虚拟机首先检查对象的Mark Word是否指向当前线程的栈帧,如果指向了说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。如果有两条以上的线程争用同一个锁,要膨胀为重量级锁,锁标志变为“10”,MarkWord中存储的就是指向重量级锁的指针,后面等待锁的线程也要进入阻塞状态。

轻量级锁解锁过程

  1. 如果对象的Mark Word仍然指向着线程的锁记录,那就用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来。
  2. 如果替换成功,整个同步过程就完成了。
  3. 如果同步替换失败,说明有其他线程尝试过获取该锁,那就要释放锁的同时,唤醒被挂起的线程。

总结轻量级锁

对于绝大部分的锁,在整个同步周期内都是不存在竞争的。但是如果存在锁竞争,那么除了互斥量开销外,还额外发生了CAS操作,会比重量级锁更慢。

偏向锁

偏向锁的目的

消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。

偏向锁定义

如果说轻量级锁是消除数据在无竞争的情况下使用CAS操作区消除同步使用的互斥量,那偏向锁就是在无竞争情况下把整个同步都消除掉,链CAS操作都不做了。

为什么叫偏向锁?

偏向锁的意思是这个锁会偏向于第一个获得它的线程,如果接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。

偏向锁的工作原理

假设当前虚拟机启用了偏向锁(-XX:+UseBiasedLocking),那么,当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word中,如果CAS操作把获取到这个锁的线程ID记录在对象的Mark Word之中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。

自动解除偏向锁

当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据对象目前是否处于被锁定的状态,撤销偏向后恢复到未锁定(标志位“01”)或轻量级锁定(标志位“00”)的状态,后续的同步操作就如轻量级锁那样执行。

总结

其实JVM层面还有读写分离锁,以及靠开发人员的代码来实现减少锁的持有时间,这些都是在进行锁的优化。 本来想着ReentrantLockSychronized各写一个代码的例子呢,但是发现书上的例子,我运行起来成了死循环了。《深入理解Java虚拟机第二版》这本书第395页的代码例子,我的jdk是1.8版本,感兴趣的读者可以在自己的环境下试试,如果有运行着不是死循环的也可以告诉我一下。从写第一篇记录读这本书的博客到现在为止,差不多正好两个月,这本书确实是本好书,里面的知识有深度,适合已经有过一定Java基础的人看,讲解的也到位,只不过是该更新了,里面讲的还是jdk1.7的内容,但是最基础的东西还都是一样的。

还有就是要感悟一下了,看这本书的目的一开始是为了面试,但是从第二章开始看,刚开始看的时候第一次把JVM的内存结构弄明白后,心里很是激动的,(因为以前总是不知道jvm的堆是什么jvm的栈是什么? ),后来就一直坚持下来了,如果说只看一遍,我感觉这本书里的内容我是啥也记不住也看不明白,这样看明白了记录下来了,印象也很深刻,里面以前一些模棱两可的知识,也得到了确认。现在这本书挑着看(有些部分感觉有些偏冷门的内容就没看,例如:程序编译与代码优化)也算是看完了,然后这周也开始投简历找工作了,只是这个时间段已经过了金三银四了,可能不那么好找工作了,不过相信自己的努力不会白费的,加油,后续若有时间了会把落下的那几章也看完了,接着我要开启新的记录(设计模式学习记录)。

JVM学习记录-线程安全与锁优化(二)的更多相关文章

  1. JVM学习记录-线程安全与锁优化(一)

    前言 线程:程序流执行的最小单元.线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址.文件I/O等),又可以独立调度(线程是C ...

  2. 【JVM.12】线程安全与锁优化

    一.概述 面向过程的编程思想极大地提升了现代软件开发的生产效率和软件可以达到的规模,但是现实世界与计算机世界之间不可避免地存在一些差异,本节就如何保证并发的正确性和如何实现线程安全讲起. 二.线程安全 ...

  3. Java并发编程学习:线程安全与锁优化

    本文参考<深入理解java虚拟机第二版> 一.什么是线程安全? 这里我借<Java Concurrency In Practice>里面的话:当多个线程访问一个对象,如果不考虑 ...

  4. jvm(13)-线程安全与锁优化(转)

    0.1)本文部分文字转自“深入理解jvm”, 旨在学习 线程安全与锁优化 的基础知识: 0.2)本文知识对于理解 java并发编程非常有用,个人觉得,所以我总结的很详细: [1]概述 [2]线程安全 ...

  5. jvm(13)-线程安全与锁优化

    [0]README 0.1)本文部分文字转自“深入理解jvm”, 旨在学习 线程安全与锁优化 的基础知识: 0.2)本文知识对于理解 java并发编程非常有用,个人觉得,所以我总结的很详细: [1]概 ...

  6. 深入理解JVM(7)——线程安全和锁优化

    Java中的线程安全 按照线程安全的“安全程度”由强至弱来排序,可以将Java语中各种操作共享的数据分为以下5类:不可变. 绝对线程安全. 相对线程安全. 线程兼容和线程对立. 1.不可变 不变的对象 ...

  7. 深入理解JVM - 线程安全与锁优化 - 第十三章

    线程安全 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对 ...

  8. JVM之java并发 ——线程安全与锁优化

    概述 人们很难想象现实中的对象在一项工作进行期间,会被不停地中断和切换,对象的属性(数据)可能会在中断期间被修改和变“脏”,而这些事情在计算机世界中则是很正常的事情.有时候,良好的设计原则不得不向现实 ...

  9. 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化

    <深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...

随机推荐

  1. Eclipse tomcat配置 未在Eclipse中添加.jar包出错

    JavaWeb: 报错信息The superclass "javax.servlet.http.HttpServlet" was not found on the Java Bui ...

  2. Strom开发配置手册

    一:Storm集群搭建 1.本次开发使用的是storm0.9.3 2.Storm0.9.3集群搭建: 1)storm集群角色包含集群主节点Nimbus:集群从节点Supervisor 2)集群安装:先 ...

  3. RPC、RMI、SOAP、WSDL的区别详解

    RPC与RMI的区别============================================================================RPC:(Remote Pr ...

  4. 1.Django入门

    MVC 大部分开发语言中都有MVC框架 MVC框架的核心思想是:解耦 降低各功能模块之间的耦合性,方便变更,更容易重构代码,最大程度上实现代码的重用 m表示model,主要用于对数据库层的封装 v表示 ...

  5. unigui如何连接数据库

    unigui如何连接数据库 UNIGUI既可以二层直连数据库,也可以通过中间件连接数据库. 这里只介绍UNIGUI二层直连数据库. 数据库连接控件.数据集控件都要拖放在MainModule窗体上.UN ...

  6. GitHub 教程【转】

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  7. SQL Server 阻塞原因分析

    这里通过连接在sysprocesses里字段值的组合来分析阻塞源头,可以把阻塞分为以下5种常见的类型(见表).waittype,open_tran,status,都是sysprocesses里的值,“ ...

  8. WPF 介绍一种在MVVM模式下弹出子窗体的方式

    主要是通过一个WindowManager管理类,在window后台代码中通过WindowManager注册需要弹出的窗体类型,在ViewModel通过WindowManager的Show方法,显示出来 ...

  9. 「PKUWC2019」拓扑序计数(状压dp)

    考场只打了 \(52\) 分暴力...\(ljc\) 跟我说了一下大致思路,我回去敲了敲. \(f[i]\) 表示状态为 \(i\) 时的方案数.我们用二进制 \(0/1\) 表示不选/选点 \(i\ ...

  10. [BZOJ2738]矩阵乘法(整体二分+二维树状数组)

    整体二分+二维树状数组. 好题啊!写了一个来小时. 一看这道题,主席树不会搞,只能用离线的做法了. 整体二分真是个好东西,啥都可以搞,尤其是区间第 \(k\) 大这种东西. 我们二分答案,然后用二维树 ...