java同步锁优化方案学习笔记(偏向锁,轻量级锁,自旋锁,重量级锁)
目录
一,概述
二,CAS算法
三,Java对象的对象头,以及Mark Word
四,偏向锁 Baised Lock
五,轻量级锁
六,自旋锁 SpinLock
七,重量级锁
八,在应用层提高锁效率的方案
一,概述
什么是java的锁?
为什么java要有锁?
java的锁为什么需要优化?
怎么优化的?
1,java中使用synchronized关键字来实现同步功能,被synchronized修饰的方法或是代码块,在多线程的情况下不能同时执行,只能挨个执行,以避免一些多线程并发的问题,这是java同步语句本身存在的意义。
2,在JDK1.5之前,事情就是像前面说的那么简单,当线程A和线程B同时执行到同步代码块时,他们会争抢同步锁,假设线程A抢到了锁,那么线程B就会从运行状态(Running)变成阻塞(Blocked)状态,直到线程A退出同步代码块,线程B才能获得锁,从阻塞状态变成运行状态,进入代码块开始执行。
3,到JDK1.6,java的开发者们不再满足于这种简单的运行-阻塞-运行模式了,因为这么在操作系统中切换线程的上下文的确挺慢,于是他们搞了一套优化的方案,也就是引入偏向锁,轻量级锁,自旋锁和重量级锁等概念,来提高同步锁的效率。从此,synchronized还是那个synchronized,用法还是那个用法,但是JVM不一定在拿不到锁的时候就直接阻塞线程了,而是有了一套更快一点的方案。
4,当锁的竞争很少或者基本没有时,JVM使用偏向锁来处理同步锁,这基本就算是没加锁。锁竞争越激烈的场景,JVM会把锁的处理方案会按照偏向锁,轻量级锁,自旋锁,重量级锁的顺序不断升级(或者叫锁的膨胀),这些锁的方案会消耗越来越多的资源,锁的效率也越来越低,所以JVM能用前面的方案就不会用后面的方案。
各个锁的具体介绍在后面。
先了解几个基本概念
二,CAS算法
CAS的全称是Compare-And-Swap,CAS算法的基本思想是这样的:当我们要改变一个变量的值时,先判断变量的值是否和某个预期值相同,如果相同则修改,如果不同则不修改。这个算法可以保证在多线程同时修改某个变量时,不会产生线程安全问题。
java中的java.util.concurrent.atomic包下的类很多都实现了这种算法,他们使用compareAndSet()方法来实现CAS,而且往往这个compareAndSet()方法对JVM来说都是原子操作,很安全。
三,Java对象的对象头,以及Mark Word
java的对象由三部分组成:对象头,实例数据,填充字节。
非数组对象的对象头由两部分组成:指向类的指针和Mark Word。
数组对象的对象头由三部分组成,比非数组对象多了块用于记录数组长度。
Mark Word用于记录对象的HashCode和锁信息等,在32位JVM中的Mark Word长度为32bit,在64位JVM中的Mark Word长度为64bit。
Mark Word的最后2bit是锁标志位,代表当前对象处于哪种锁状态,当Mark Word处于不同的锁状态时,Mark Word记录的信息也有所不同。
32位JVM中不同锁状态的Mark Word记录的信息如下表:
锁状态 |
25bit |
4bit |
1bit |
2bit |
|
23bit |
2bit |
是否偏向锁 |
锁标志位 |
||
无锁 |
对象的HashCode |
分代年龄 |
0 |
01 |
|
偏向锁 |
线程ID |
Epoch |
分代年龄 |
1 |
01 |
轻量级锁 |
指向栈中锁记录的指针 |
00 |
|||
重量级锁 |
指向重量级锁的指针 |
10 |
|||
GC标记 |
空 |
11 |
可以看到无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态。
随着锁的升级,Mark Word里面的数据就会按照上表不断变化,JVM也会按照Mark Word里面的信息来判断对象锁处于什么状态。
关于对象头和Mark Word的详细介绍见下面的连接:
https://blog.csdn.net/lkforce/article/details/81128115
下面单独介绍各个锁
四,偏向锁 Baised Lock
偏向二字就是字面上的意思,说的是第一个试图获取锁的线程,JVM会把锁对象的Mark Word从无锁状态变成偏向锁状态,并把线程id记在锁对象的Mark Word中,当这个线程以后还想要获取这个锁时,JVM发现这个锁对象处于偏向锁状态,而且线程id就是这个线程自己,就直接让他通过,不用再进行争抢锁的操作了,省了CAS操作的时间。
当然这个特权只有第一个获取锁的线程才能拥有,这也就是偏向二字的意思。
如果有第二个线程想要来争抢锁,JVM发现锁对象处于偏向锁状态,而且线程id是另外一个线程,新线程会使用CAS操作试图争抢对象锁,如果成功,Word Mark中的线程id就会替换为新线程的id,如果失败,这个偏向锁就会升级为轻量级锁,同样也是改锁对象的Mark Word。
由此可见,偏向锁是一种用缓存空间换时间的方案,在锁竞争不是很激烈的情况下会很有用,如果竞争比较激烈,JVM先使用偏向锁然后又不断进行锁升级,锁的效率会下降。
启用偏向锁的方式:
-XX:+UseBiasedLocking
-XX:BiasedLockingStartupDelay=0
关闭偏向锁的方式:
-XX:-UseBiasedLocking
五,轻量级锁
如果对象锁升级为轻量级锁,JVM会在当前线程的线程栈中开辟一块单独的空间叫锁记录(Lock Record),锁记录由两部分组成,分别是Displaced hdr和Owner。
JVM会把锁对象的Mark Word复制进去,然后把在对象Mark Word中保存指向锁记录的指针,并在锁记录的Owner中保存指向Mark Word的指针。这两个保存操作都是CAS操作。
如果保存成功,则表示当前线程获得该轻量级锁,修改锁对象的Mark Word锁标志位为00。
如果保存失败,JVM就检查锁对象的Mark Word是否已经保存了指向当前线程的指针,如果有则说明当前线程已经获得了这个锁,可以继续执行。如果没有指向当前线程的指针,则代表抢锁失败。
当前线程抢锁失败后会用自旋锁重试抢锁,如果一直失败,当前锁会升级为重量级锁,线程会被阻塞,锁对象的Mark Word标志位也会改为10。
六,自旋锁 SpinLock
spin在英文中用于描述纺纱的纱轮疯狂自转的样子,瞧这名字起的,一看就很耗CPU。
自旋锁其实并不属于锁的状态,从Mark Word的说明可以看到,并没有一个锁状态叫自旋锁。所谓自旋其实指的就是自己重试,当线程抢锁失败后,重试几次,要是抢到锁了就继续,要是抢不到就阻塞线程。说白了还是为了尽量不要阻塞线程。
由此可见,自旋锁是是比较消耗CPU的,因为要不断的循环重试,不会释放CPU资源。另外,加锁时间普遍较短的场景非常适合自旋锁,可以极大提高锁的效率。
在JDK1.6之前,自旋锁可以用参数来确定是否启用,以及自旋的次数:
-XX:+UseSpinning 启用自旋锁
-XX:PreBlockSpin=10 自旋次数10次
而从JDK1.7开始,自旋锁默认启用,而且JVM有了一套确认自旋次数和自旋周期的方案:
1,如果平均负载小于CPUs则一直自旋。
2,如果有超过(CPUs/2)个线程正在自旋,则后来线程直接阻塞。
3,如果正在自旋的线程发现Owner发生了变化则延迟自旋时间(自旋计数)或进入阻塞。
4,如果CPU处于节电模式则停止自旋。
5,自旋时间的最坏情况是CPU的存储延迟(CPU A存储了一个数据,到CPU B得知这个数据之间的时间差)。
6,自旋时会适当放弃线程优先级之间的差异。
java代码可以实现类似自旋锁的功能,网上有很多,我贴一个jetty中写的自旋锁的例子:
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock
{private final AtomicReference<Thread> _lock = new AtomicReference<>(null);private final Lock _unlock = new Lock();public Lock lock(){Thread thread = Thread.currentThread();while(true){if (!_lock.compareAndSet(null,thread)){if (_lock.get()==thread)throw new IllegalStateException("SpinLock is not reentrant");continue;}return _unlock;}}public boolean isLocked(){return _lock.get()!=null;}public boolean isLockedThread(){return _lock.get()==Thread.currentThread();}public class Lock implements AutoCloseable{@Overridepublic void close(){_lock.set(null);}}
}
主要是思路就是用AtomicReference类的compareAndSet()方法,这个方法是原子操作,通过不断循环来重试获得锁。
七,重量级锁
重量级锁就是java最原始的同步锁,抢不到锁的线程就会被阻塞,在等待池中等待激活。
这种锁不是公平锁,来的早的线程不一定优先激活。
关于线程的等待池,JVM也有一套完整的运行方案。
八,在应用层提高锁效率的方案
上面所说的锁的优化方案都是在JVM内部的,由JVM自己搞定。在应用层,开发者也可以采取一些措施,提高锁的效率。
1,减少锁的持有时间
指的是不需要同步执行的代码,不要放在同步代码块中。同步块中代码减少,锁的持续时间短,锁的性能会有所提高。
2,减小锁粒度
指的是把资源分批使用不同的锁,不同批次的资源的操作互不影响。
比如ConturrentHashMap类,把map分成多段,每段一个锁,不在一段的数据可以同时修改。
3,锁分离
把关系不大的操作使用不同的锁,使这些操作互不影响。
比如LinkedBlockingQueue类,从队列头获取数据的take()方法和从队列末尾添加数据的put()方法分别使用不同的锁,两者互不影响。
4,锁粗化
指的是当虚拟机需要连续对同一把锁进行加锁和释放时,尽量改成只使用一次锁。
比如连续多个synchronized语句块,或循环中的synchronized语句块,用的是同一个对象作为锁,那还不如直接用一个synchronized语句块把他们都包含起来。
5,弃用synchronized关键字
不使用synchronized关键字,可以自己编写代码实现类似偏向锁、自旋锁的功能,减少因为同步锁而带来的效率损耗。比如上文中jetty自己实现的自旋锁。
以上。
java同步锁优化方案学习笔记(偏向锁,轻量级锁,自旋锁,重量级锁)相关推荐
- Java开发面试高频考点学习笔记(每日更新)
Java开发面试高频考点学习笔记(每日更新) 1.深拷贝和浅拷贝 2.接口和抽象类的区别 3.java的内存是怎么分配的 4.java中的泛型是什么?类型擦除是什么? 5.Java中的反射是什么 6. ...
- 拉勾网《32个Java面试必考点》学习笔记之十一------消息队列与数据库
本文为拉勾网<32个Java面试必考点>学习笔记.只是对视频内容进行简单整理,详细内容还请自行观看视频<32个Java面试必考点>.若本文侵犯了相关所有者的权益,请联系:txz ...
- Linux性能优化实战学习笔记:第十讲==中断
Linux性能优化实战学习笔记:第十讲 一.坏境准备 1.拓扑图 2.安装包 在第9节的基础上 在VM2上安装hping3依奈包 ? 1 2 3 4 5 6 7 wget http://www.tcp ...
- Java虚拟机(JVM)学习笔记(不定时更新)
Java虚拟机(JVM)学习笔记 不少组织都曾开发过Java虚拟机: SUN公司曾经使用过3个虚拟机,Classic.Exact VM.Hotspot. 其中Hotspot虚拟机沿用至今,并已 ...
- 从零开始带你成为MySQL实战优化高手学习笔记(一)
重复是有必要的. 很多新入职的小朋友可能和现在的我一样,对数据库的了解仅仅停留在建库建表增删改查这些操作,日常工作也都是用封装好的代码,别说底层原理了,数据库和系统之间是如何工作都不是很懂. 长此以往 ...
- 拉勾网《32个Java面试必考点》学习笔记之一------Java职业发展路径
本文为拉勾网<32个Java面试必考点>学习笔记.只是对视频内容进行简单整理,详细内容还请自行观看视频<32个Java面试必考点>.若本文侵犯了相关所有者的权益,请联系:txz ...
- Windows事件等待学习笔记(一)—— 临界区自旋锁
Windows事件等待学习笔记(一)-- 临界区&自旋锁 基础知识 演示代码 案例一 案例二 LOCK 单行代码原子操作 多行代码原子操作 临界区 演示代码 手动实现 自旋锁 分析 KeAcq ...
- Linux性能优化实战学习笔记:第四十六讲=====实战分析
Linux性能优化实战学习笔记:第四十六讲 一.上节回顾 不知不觉,我们已经学完了整个专栏的四大基础模块,即 CPU.内存.文件系统和磁盘 I/O.以及网络的性能分析和优化.相信你已经掌握了这些基础模 ...
- 杨晓峰-java核心技术36讲(学习笔记)- 第1讲 | 谈谈你对Java平台的理解?
杨晓峰-java核心技术36讲(学习笔记) 接下来我会分享杨晓峰-java核心技术36讲的学习笔记,内容较多,补充了其中一些牛人评论,相对详细(仅供个人学习记录整理,希望大家支持正版:https:// ...
最新文章
- MySQL排序原理与MySQL5.6案例分析【转】
- SVN系列操作(二)svn不显示图标的解决方法
- springboot-swagger2
- 【画出漂亮的电路图】CircuiTikZ库学习第一天
- Ian Thiel:靠这 3 点,实现 30 倍增长,从不盈利到营收 5.5 亿
- Jena增删改查java API
- 收集常用电路基础公式换算
- 智能不属于计算机理论,讨论:计算机如何能提出自己的理论
- codeforces B. The Fibonacci Segment 解题报告
- php导出csv文件,可导出前导0实例
- utl_file包的使用
- 售货员的难题(codevs 2596)
- midas显示代理服务器错误,midas运行后出错大神帮忙看看哪错了
- Apollo学习笔记(8)车辆动力学模型
- JESD204B调试1
- mysql count(0)_sql中count(0)是什么意思?
- php做后端的优势,【后端开发】php和java的优势
- Java 图形界面(满天星星)
- 软件工程-体系结构设计
- 一个sql语句的编写 写出不及格门数大于等于2的学生的姓名和平均成绩