目录

一,概述

二,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同步锁优化方案学习笔记(偏向锁,轻量级锁,自旋锁,重量级锁)相关推荐

  1. Java开发面试高频考点学习笔记(每日更新)

    Java开发面试高频考点学习笔记(每日更新) 1.深拷贝和浅拷贝 2.接口和抽象类的区别 3.java的内存是怎么分配的 4.java中的泛型是什么?类型擦除是什么? 5.Java中的反射是什么 6. ...

  2. 拉勾网《32个Java面试必考点》学习笔记之十一------消息队列与数据库

    本文为拉勾网<32个Java面试必考点>学习笔记.只是对视频内容进行简单整理,详细内容还请自行观看视频<32个Java面试必考点>.若本文侵犯了相关所有者的权益,请联系:txz ...

  3. Linux性能优化实战学习笔记:第十讲==中断

    Linux性能优化实战学习笔记:第十讲 一.坏境准备 1.拓扑图 2.安装包 在第9节的基础上 在VM2上安装hping3依奈包 ? 1 2 3 4 5 6 7 wget http://www.tcp ...

  4. Java虚拟机(JVM)学习笔记(不定时更新)

    Java虚拟机(JVM)学习笔记 不少组织都曾开发过Java虚拟机: SUN公司曾经使用过3个虚拟机,Classic.Exact VM.Hotspot.     其中Hotspot虚拟机沿用至今,并已 ...

  5. 从零开始带你成为MySQL实战优化高手学习笔记(一)

    重复是有必要的. 很多新入职的小朋友可能和现在的我一样,对数据库的了解仅仅停留在建库建表增删改查这些操作,日常工作也都是用封装好的代码,别说底层原理了,数据库和系统之间是如何工作都不是很懂. 长此以往 ...

  6. 拉勾网《32个Java面试必考点》学习笔记之一------Java职业发展路径

    本文为拉勾网<32个Java面试必考点>学习笔记.只是对视频内容进行简单整理,详细内容还请自行观看视频<32个Java面试必考点>.若本文侵犯了相关所有者的权益,请联系:txz ...

  7. Windows事件等待学习笔记(一)—— 临界区自旋锁

    Windows事件等待学习笔记(一)-- 临界区&自旋锁 基础知识 演示代码 案例一 案例二 LOCK 单行代码原子操作 多行代码原子操作 临界区 演示代码 手动实现 自旋锁 分析 KeAcq ...

  8. Linux性能优化实战学习笔记:第四十六讲=====实战分析

    Linux性能优化实战学习笔记:第四十六讲 一.上节回顾 不知不觉,我们已经学完了整个专栏的四大基础模块,即 CPU.内存.文件系统和磁盘 I/O.以及网络的性能分析和优化.相信你已经掌握了这些基础模 ...

  9. 杨晓峰-java核心技术36讲(学习笔记)- 第1讲 | 谈谈你对Java平台的理解?

    杨晓峰-java核心技术36讲(学习笔记) 接下来我会分享杨晓峰-java核心技术36讲的学习笔记,内容较多,补充了其中一些牛人评论,相对详细(仅供个人学习记录整理,希望大家支持正版:https:// ...

最新文章

  1. MySQL排序原理与MySQL5.6案例分析【转】
  2. SVN系列操作(二)svn不显示图标的解决方法
  3. springboot-swagger2
  4. 【画出漂亮的电路图】CircuiTikZ库学习第一天
  5. Ian Thiel:靠这 3 点,实现 30 倍增长,从不盈利到营收 5.5 亿
  6. Jena增删改查java API
  7. 收集常用电路基础公式换算
  8. 智能不属于计算机理论,讨论:计算机如何能提出自己的理论
  9. codeforces B. The Fibonacci Segment 解题报告
  10. php导出csv文件,可导出前导0实例
  11. utl_file包的使用
  12. 售货员的难题(codevs 2596)
  13. midas显示代理服务器错误,midas运行后出错大神帮忙看看哪错了
  14. Apollo学习笔记(8)车辆动力学模型
  15. JESD204B调试1
  16. mysql count(0)_sql中count(0)是什么意思?
  17. php做后端的优势,【后端开发】php和java的优势
  18. Java 图形界面(满天星星)
  19. 软件工程-体系结构设计
  20. 一个sql语句的编写 写出不及格门数大于等于2的学生的姓名和平均成绩

热门文章

  1. 计算机应用基础总体目标,《计算机应用基础》整体设计
  2. 威纶通宏开机后使用初始化宏指令_维纶触摸屏常用设置与重要指令
  3. 钛备份-Android备份神器
  4. 惠普服务器ssa找不到控制卡,惠普ssacli工具使用
  5. oracle 检查dmp文件格式,查看oracle数据库dmp文件的字符集信息
  6. 避免死锁: 银行家算法
  7. 解决ico图标不透明
  8. windows系统封装
  9. Everything Has Changed ( HDU 6354 ) 简单公式
  10. Latex001 | 我走过的弯路之一——如何让Winedt编译中文内容