参考

https://blog.csdn.net/varyall/article/details/79698145

《深入理解Java虚拟机》

锁状态:无锁、偏向锁、轻量级锁、重量级锁(具体是何种状态,取决于竞争情况

这三个只是代表锁的状态。而非动作。

首先了解为什么锁的是对象

HotSpot虚拟机的对象的内存分布主要分为三个部分:

  • 对象头(分为两部分)

    1. 第一部分用于存储对象自身的运行时数据,如哈希吗,GC分代年龄(又称为Mark Word)

    2. 第二部分用于存储指向方法区对象类型数据的指针。即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。

  • 实例数据(真正存储有效信息)

  • 对齐填充(非必须,仅起着占位符的作用)

    HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,即对象大小必须是8字节的整数倍。而对象头正好是8字节的整数倍,而对象实例数据部分没有对齐,需要通过对齐填充来补全。

虚拟机通过Mark Word的信息来区分锁的状态:

Epoch:偏向时间戳

轻量级锁、重量级锁、偏向锁

这四种状态都不是Java语言中的锁,而是Jvm为了提高锁的获取与释放效率而做的优化

四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。

  • 轻量级锁:本意是在没有多线程竞争的前提下,通过CAS操作而避免了使用互斥量的开销。

    在只有两个线程的情况下,提高性能。

  • 偏向锁:大多数情况下,锁不存在多线程竞争,而是总是由同一线程多次获得时,为了使线程获得锁的代价更低而引入了偏向锁。提高了带有同步,但无竞争的程序性能。

    在只有一个线程的情况下,进一步提高性能。

轻量级锁的原理

  1. 虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间。

  2. 将还没有被锁定的同步对象的对象头中的Mark Word拷贝到锁记录空间中

  3. 拷贝成功后,使用CAS操作将对象头中的Mark Word修改为一个指针(指向此线程的锁记录空间)

  4. 如果CAS操作成功,这个线程就拥有了该对象锁,并将锁标志“01”改为“00”,即表示此对象处于轻量级锁的状态。此时,如果有另一个线程竞争轻量级锁,则会发生自旋。

  5. 如果失败了,说明这个Mark Word已经被别的线程拿走了,如果存在两条以上的线程争夺同一个锁,那么轻量级锁就会膨胀为重量级锁。锁标志则被修改为“10”。膨胀之后,不可逆转。变成重量级锁之后,其他竞争失败的线程不会自旋,而是阻塞。(大量线程自旋,极大浪费CPU)

解锁过程:

  1. 依然是CAS操作,将上面被替换的Mark Word再替换回对象头中。

  2. 替换失败,说明轻量级锁已经膨胀成了重量级锁,所以CAS操作替换失败,此时,一定有其他线程在抢夺锁的过程中被阻塞,这时候,释放锁的同事,唤醒被挂起的线程。

偏向锁

轻量级锁的获取和释放都依赖于CAS原子操作,而偏向锁是在无竞争的情况下,连CAS操作都不做了。只有在需要置换ThreadID的时候,执行一次CAS原子操作。在只有一个线程执行同步块的情况下,进一步提高性能。

偏向锁的获取过程:

  1. 在对象第一次被线程获取的时候,查看Mark Word中偏向锁标志是否为1,锁标志是否为01。是否已经确定Thread ID,如果已经确定,则不需要CAS操作,直接执行同步代码块。

  2. 如果没有确定Thread ID,则通过CAS操作竞争锁,成功:则将Mark Word中的Thread ID设置为当前线程ID,然后执行同步代码块

  3. 如果竞争失败:则说明有线程一同竞争,偏向锁会升级为轻量级锁。竞争失败的线程自旋。

偏向锁的释放:

如果没有竞争,线程不会主动释放偏向锁,只有在有竞争的情况下,偏向锁会被撤销,提升为轻量级锁。

三种锁的转化

三种锁对比

自旋锁

线程的阻塞,挂起,唤醒,都会给系统性能带来压力。

有时候线程只需要等待很短的时间,这个时间将线程挂机,就很不值得。

所以自旋技术:让线程执行一个忙循环(自旋),稍等一下。这就是自旋锁。

-XX:+UseSpinning开启自旋锁。JDK1.6之后,默认开启。

自旋锁的效果:避免了线程切换的开销,但是在自旋过程中要占用CPU。如果锁被占用很短,自旋时间很短,自旋等待的效果就会很好,反之,如果锁占用时间长,自旋就会很浪费资源。

JDK1.6之前自旋默认循环次数为10次。可以使用参数-XX:PreBlockSpin来修改。

JDK1.6之后,次数不在固定,可以自适应,由同一个锁的前一个自旋时间来决定。比如:上一个等待此锁的线程自旋了多久,这次系统就会让此线程自旋多久。

锁消除

对于堆内存中永远不会逃逸,不会被其他线程访问到的变量,就认为是线程私有的,无需同步加锁。

虚拟机即时编译器在运行时,如果检测到不可能存在共享数据竞争的锁,说明这个锁无意义,就会自动进行消除这个锁。这就是锁消除。

// 看起来没有同步的方法
public String concatString(String str1,String str2,String str3){
return str1 + str2 + str3;
}
// 在JDK1.5之前,会自动转化为StringBuffer进行append()操作
public String concatString(String str1,String str2,String str3){
StringBuffer sb = new StringBuffer();
sb.append(str1);
sb.append(str2);
sb.append(str3);
return sb.toString();
}

锁粗化

原则上,将同步块的作用范围限制的尽量小,在多线程中的互斥同步的操作数量就会尽可能的小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。

大部分情况下,这是正确的。但是,如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作在循环体中,即使没有竞争,频繁互斥同步也会导致不必要的性能损耗。

锁粗化:如果虚拟机检测到有这样一连串零碎操作(如上面的代码)都对同一个对象加锁,将会把加锁同步的范围扩大到整个操作序列的外部。

【Java】锁机制的更多相关文章

  1. 转 : 深入解析Java锁机制

    深入解析Java锁机制 https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&mid=2247485524&idx=1&s ...

  2. Java 锁机制总结

    锁的种类 独享锁 VS 共享锁 独享锁:锁只能被一个线程持有(synchronized) 共享锁:锁可以被多个程序所持有(读写锁) 乐观锁 VS 悲观锁 乐观锁:每次去拿数据的时候都乐观地认为别人不会 ...

  3. java锁机制的面试题

    java锁机制的面试题 1.ABA问题 2.CAS乐观锁 3.synchronize实现原理 4.synchronize与lock的区别 5.volatile实现原理 6.乐观锁的业务场景及实现方式 ...

  4. Java锁机制深入理解

    Java锁机制 背景知识 指令流水线 ​ CPU的基本工作是执行存储的指令序列,即程序.程序的执行过程实际上是不断地取出指令.分析指令.执行指令的过程. ​ 几乎所有的冯•诺伊曼型计算机的CPU,其工 ...

  5. java锁机制

    2.4 锁机制        临界区是指,使用同一个锁控制的同一段代码区或多段代码区之间,在同一时间内最多只能有一个线程在执行操作.这个概念与传统的临界区有略微的差别,这里不想强调这些概念上的差别,临 ...

  6. Java锁机制了解一下

    前言 回顾前面: 多线程三分钟就可以入个门了! Thread源码剖析 多线程基础必要知识点!看了学习多线程事半功倍 只有光头才能变强! 本文章主要讲的是Java多线程加锁机制,有两种: Synchro ...

  7. JAVA锁机制-可重入锁,可中断锁,公平锁,读写锁,自旋锁,

    如果需要查看具体的synchronized和lock的实现原理,请参考:解决多线程安全问题-无非两个方法synchronized和lock 具体原理(百度) 在并发编程中,经常遇到多个线程访问同一个 ...

  8. JAVA锁机制(上)

    在实际开发中经常会用到多线程协作来处理问题,锁是处理线程安全不可缺少的机制.在JAVA中可以通过至少三种方式来实现线程锁. 1.  synchronized修饰符,这种锁机制是虚拟机实现的一种锁. 2 ...

  9. java 锁机制(synchronized 与 Lock)

    在java中,解决同步问题,很多时候都会使用到synchronized和Lock,这两者都是在多线程并发时候常使用的锁机制. synchronized是java中的一个关键字,也就是说是java内置的 ...

  10. 【面试专栏】JAVA锁机制

    1. 悲观锁 / 乐观锁   在Java和数据库中都存在悲观锁和乐观锁的应用.Mysql锁机制中的悲观锁和乐观锁请查看:   Mysql锁机制--悲观锁和乐观锁   悲观锁:在获得数据时先加锁,只到数 ...

随机推荐

  1. 善用mysql中的FROM_UNIXTIME()函数和UNIX_TIMESTAMP()函数

    我们经常会面临要从数据库里判断时间,取出特定日期的查询.但是数据库里储存的都是unix时间戳,处理起来并不是特别友好.幸而MYSQL提供了几个处理时间戳的函数,可以帮助我们在查询的时候,就将时间戳格式 ...

  2. JAVA微信开发-如何保存包含特殊字符的微信昵称

    我们在做微信开发的时候,有一个很重要的就是通过openid获取用户的详细信息,包含昵称,头像,省,市,区的信息,但是现在移动时代,很多人追求个性,在名字当中大量使用火星文或者表情符.(本人实际测试过一 ...

  3. linux环境,无dig命令-bash: dig: command not found?

    背景描述: 今天使用dig命令,报错命令不存在,-bash: dig: command not found 解决: 通过yum方式安装 yum -y install bind-utils 备注:之前尝 ...

  4. WebGL学习笔记(七):输入和动画

    目前为止,我们绘制出来的3D物体都是静止的,接下来我们需要让桌面上的小盒子可以根据我们按键(上下键)前进后退: 输入方面,监听按键和鼠标消息直接在document上添加对应的监听就行了: 动画这块,我 ...

  5. springMVC和struts2有什么不同?为什么要用springMVC或者struts2?让你实现一个MVC框架大概如何设计?

    [问题一:不同] (1)框架机制 1.Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用S ...

  6. Swift编码总结8

    1.判断当前控制器是否在显示: // 判断当前控制器是否在显示 func isCurrentViewControllerVisible() -> Bool { return (self.isVi ...

  7. [LeetCode] 199. Binary Tree Right Side View 二叉树的右侧视图

    Given a binary tree, imagine yourself standing on the right side of it, return the values of the nod ...

  8. [LeetCode] 342. Power of Four 4的次方数

    Given an integer (signed 32 bits), write a function to check whether it is a power of 4. Example:Giv ...

  9. [LeetCode] 666. Path Sum IV 二叉树的路径和 IV

    If the depth of a tree is smaller than 5, then this tree can be represented by a list of three-digit ...

  10. Kubernetes 控制器之 Service 讲解(七)

    一.背景介绍 我们这里准备三台机器,一台master,两台node,采用kubeadm的方式进行安装的,安装过程大家可以参照我之前的博文. IP 角色 版本 192.168.1.200 master ...