- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章锁的四种状态与锁升级过程由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
。
锁的状态总共有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁,这四种锁状态分别代表什么,为什么会有锁升级?其实在 JDK 1.6之前,synchronized 还是一个重量级锁,是一个效率比较低下的锁,但是在JDK 1.6后,Jvm为了提高锁的获取与释放效率对(synchronized )进行了优化,引入了 偏向锁 和 轻量级锁 ,从此以后锁的状态就有了四种(无锁、偏向锁、轻量级锁、重量级锁),并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,也就是说只能进行锁升级(从低级别到高级别),不能锁降级(高级别到低级别),意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率.
。
在 synchronized 最初的实现方式是 “阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,这种方式就是 synchronized实现同步最初的方式,这也是当初开发者诟病的地方,这也是在JDK6以前 synchronized效率低下的原因,JDK6中为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”.
所以目前锁状态一种有四种,从级别由低到高依次是:无锁、偏向锁,轻量级锁,重量级锁,锁状态只能升级,不能降级 。
如图所示:
。
。
。
synchronized 用的锁是存在Java对象头里的,那么什么是对象头呢?
5.1 Java 对象头 。
我们以 Hotspot 虚拟机为例,Hopspot 对象头主要包括两部分数据:MarkWord(标记字段)和KlassPointer(类型指针) 。
Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化.
Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.
在上面中我们知道了, synchronized 用的锁是存在Java对象头里的,那么具体是存在对象头哪里呢?答案是:存在锁对象的对象头的Mark Word中,那么MarkWord在对象头中到底长什么样,它到底存储了什么呢?
在64位的虚拟机中:
在32位的虚拟机中:
。
下面我们以 32位虚拟机为例,来看一下其 Mark Word 的字节具体是如何分配的 。
无锁:对象头开辟 25bit 的空间用来存储对象的 hashcode ,4bit 用于存放对象分代年龄,1bit 用来存放是否偏向锁的标识位,2bit 用来存放锁标识位为01 。
偏向锁: 在偏向锁中划分更细,还是开辟 25bit 的空间,其中23bit 用来存放线程ID,2bit 用来存放 Epoch,4bit 存放对象分代年龄,1bit 存放是否偏向锁标识, 0表示无锁,1表示偏向锁,锁的标识位还是01 。
轻量级锁:在轻量级锁中直接开辟 30bit 的空间存放指向栈中锁记录的指针,2bit 存放锁的标志位,其标志位为00 。
重量级锁: 在重量级锁中和轻量级锁一样,30bit 的空间用来存放指向重量级锁的指针,2bit 存放锁的标识位,为11 。
GC标记: 开辟30bit 的内存空间却没有占用,2bit 空间存放锁标志位为11.
其中无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态 。
关于内存的分配,我们可以在git中openJDK中 markOop.hpp 可以看出:
5.2 Monitor 。
Monitor 可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个 Java 对象就有一把看不见的锁,称为内部锁或者 Monitor 锁.
Monitor 是线程私有的数据结构,每一个线程都有一个可用 monitor record 列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个 monitor 关联,同时 monitor 中有一个 Owner 字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用.
Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为重量级锁.
随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking=false来禁用偏向锁.
。
6.2 无锁 。
无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功.
无锁的特点是修改操作会在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功.
6.3 偏向锁 。
初次执行到synchronized代码块的时候,锁对象变成偏向锁(通过CAS修改对象头里的锁标志位),字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后,线程并不会主动释放偏向锁。当第二次到达同步代码块时,线程会判断此时持有锁的线程是否就是自己(持有锁的线程ID也在对象头里),如果是则正常往下执行。由于之前没有释放锁,这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高.
偏向锁是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能.
当一个线程访问同步代码块并获取锁时,会在 Mark Word 里存储锁偏向的线程 ID。在线程进入和退出同步块时不再通过 CAS 操作来加锁和解锁,而是检测 Mark Word 里是否存储着指向当前线程的偏向锁。轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换 ThreadID 的时候依赖一次 CAS 原子指令即可.
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的.
关于偏向锁的撤销,需要等待全局安全点,即在某个时间点上没有字节码正在执行时,它会先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁,恢复到无锁(标志位为01)或轻量级锁(标志位为00)的状态.
6.4 轻量级锁(自旋锁) 。
。
轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋(关于自旋的介绍见文末)的形式尝试获取锁,线程不会阻塞,从而提高性能.
轻量级锁的获取主要由两种情况:① 当关闭偏向锁功能时;② 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁.
一旦有第二个线程加入锁竞争,偏向锁就升级为轻量级锁(自旋锁)。这里要明确一下什么是锁竞争:如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争.
在轻量级锁状态下继续锁竞争,没有抢到锁的线程将自旋,即不停地循环判断锁是否能够被成功获取。获取锁的操作,其实就是通过CAS修改对象头里的锁标志位。先比较当前锁标志位是否为“释放”,如果是则将其设置为“锁定”,比较并设置是原子性发生的。这就算抢到锁了,然后线程将当前锁的持有者信息修改为自己.
长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)。如果多个线程用一个锁,但是没有发生锁竞争,或者发生了很轻微的锁竞争,那么synchronized就用轻量级锁,允许短时间的忙等现象。这是一种折衷的想法,短时间的忙等,换取线程在用户态和内核态之间切换的开销.
6.4 重量级锁 。
重量级锁显然,此忙等是有限度的(有个计数器记录自旋次数,默认允许循环10次,可以通过虚拟机参数更改)。如果锁竞争情况严重,某个达到最大自旋次数的线程,会将轻量级锁升级为重量级锁(依然是CAS修改锁标志位,但不修改持有锁的线程ID)。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒.
重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态.
简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资 。
5、总结 。
文中讲述了锁的四种状态以及锁是如何一步一步升级的过程, 。
原文地址:https://mp.weixin.qq.com/s/Lnx1j4njDymcxkAK2ZbwAg 。
最后此篇关于锁的四种状态与锁升级过程的文章就讲到这里了,如果你想了解更多关于锁的四种状态与锁升级过程的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
如果我声明了类似的类型 type test(NSIZE) integer, len :: NSIZE real :: dummy(NSIZE) contains procedure,
我知道这是一个不太可能的事情,但是由于“选项私有(private)模块”的限制,甚至更糟糕的“私有(private)子/函数”的限制,有谁知道是否有一种方法可以从 Excel 应用程序隐藏 VBA 过
我有两个表,property 和 component。 component.id_property = property.id。 我正在尝试创建一个过程,该过程对所选属性的组件进行计数,如果所选属性没
我有一份报告,它是在 SSRS 2005 中开发的,我正在使用存储过程从数据库中获取结果。报告输出的结果非常简单,如下图所示。 如果假设我正在寻找不同的成员 例如:- MemberID c108 c
我需要一个通用函数/过程,该函数/过程将根据提供的数据计算出我的淡入淡出时间和值,如下所示: 我将字节值保存在字节数组中:这些是起始值。然后,我在其他数组中存储了一些值:这些将是新值。然后我有时间要提
我想在界面的多个按钮上创建相同的操作。是否只能通过创建单独的操作监听器方法并调用执行操作的方法才可行,还是还有其他方法?是否可以将按钮放在一个组中并执行以下操作:- groupButton.setOn
我有以下情况: procedure Test; begin repeat TryAgain := FALSE; try // Code // Code if this an
我正在尝试执行以下操作;假设我在 Oracle 中创建了一个对象类型 create type test as object( name varchar2(12), member procedure p
问题: 如果可能的话,如何声明一个用于任何类型参数的函数 T其中 T 的唯一约束是它被定义为 1D array如 type T is array ( integer range <> ) of a_r
我正在尝试创建这个 mysql 过程来制作一个包含今年所有日期和所有时间的表(以一小时为间隔。) CREATE TABLE FECHAS ( created_at datetime ); CREA
所以, 我在这里面临一个问题,这让我发疯,我认为这是一个愚蠢的错误,所以我不是 MySQL 的新手,但它并不像我想象的那样工作。 尝试将此语句部署到 MySQL 后,我收到此错误: ERROR 106
我有一个架构,其中包含星球大战中的人物列表、他们出现的电影、他们访问的行星等。这是架构: CREATE DATABASE IF NOT EXISTS `starwarsFINAL` /*!40100
我一直在为一家慈善机构创建一款应用程序,允许家庭在节日期间注册接收礼物。数据库组织有多个表。下面列出了这些表(及其架构/创建语句): CREATE TABLE IF NOT EXISTS ValidD
正如上面标题所解释的,我正在尝试编写一个sql函数来按日期删除表而不删除系统表。我在此消息下方放置了一张图片,以便直观地解释我的问题。任何帮助将不胜感激!感谢您的时间! 最佳答案 您可以通过查询INF
DELIMITER $$ CREATE PROCEDURE INSERT_NONE_HISTORY_CHECKBOX() BEGIN DECLARE note_id bigint(20); F
是否可以编写一个存储过程或触发器,在特定时间在数据库内部自动执行,而无需来自应用程序的任何调用?如果是,那么任何人都可以给我一个例子或链接到一些我可以阅读如何做到这一点的资源。 最佳答案 查看 pgA
我需要创建一个过程:1)从表中的字段中选择一些文本并将其存储在变量中2) 更新相同的记录字段,仅添加 yyyymmdd 格式的日期以及过程中的附加文本输入...类似这样的... delimiter /
好的,这就是我想做的: 如果条目已存在(例如基于字段name),则只需返回其id 如果没有,请添加 这是我迄今为止所管理的(对于“如果不存在,则创建它”部分): INSERT INTO `object
以下是我编写的程序,用于找出每位客户每天购买的前 10 件商品。 这是我尝试过的第一个 PL/SQL 操作。它没有达到我预期的效果。 我使用的逻辑是接受开始日期、结束日期以及我对每个客户感兴趣的前“x
我正在尝试在MySQL中创建一个过程那insert week s(当年)发送至我的 week table 。但存在一个问题,因为在为下一行添加第一行后,我收到错误: number column can
我是一名优秀的程序员,十分优秀!