Lock / synchronized

Lock锁的基本操作是通过乐观锁实现的,由于Lock锁也会在阻塞时被挂起,依然属于悲观锁

  synchronized Lock
实现方式 JVM层实现 Java底层代码实现
锁的获取 JVM隐式获取 lock() / tryLock() / tryLock(timeout, unit) / lockInterruptibly()
锁的释放 JVM隐式释放 unlock()
锁的类型 非公平锁、可重入 非公平锁/公平锁、可重入
锁的状态 不可中断 可中断
锁的性能 高并发下会升级为重量级锁 更稳定

实现原理

  1. Lock锁是基于Java实现的锁,Lock是一个接口

    • 常见的实现类:ReentrantLock、ReentrantReadWriteLock,都是依赖AbstractQueuedSynchronizer(AQS)实现
  2. AQS中包含了一个基于链表实现的等待队列(即CLH队列),用于存储所有阻塞的线程
  3. AQS中有一个state变量,该变量对ReentrantLock来说表示加锁状态
  4. AQS中的CLH队列的所有操作均通过CAS操作实现的

锁分离优化

ReentrantReadWriteLock

  1. ReentrantLock是一个独占锁,同一时间只允许一个线程访问
  2. ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问
    • ReentrantReadWriteLock内部维护了两把锁,一把用于读操作的ReadLock,一把用于写操作的WriteLock
  3. ReentrantReadWriteLock如何保证共享资源的原子性?ReentrantReadWriteLock也是基于AQS实现的
    • 自定义同步器(继承AQS)需要在同步状态state上维护多个读线程和一个写线程的状态
    • ReentrantReadWriteLock利用了高低位,来实现一个整型控制两种状态的功能
      • 将同步状态state切分为两部分,高16位表示读,低16位表示写

获取写锁

  1. 一个线程尝试获取写锁时,会先判断同步状态state是否为0

    • 如果state为0,说明暂时没有其他线程获取锁
    • 如果state不为0,说明其它线程获取了锁
  2. 当state不为0时,会再去判断同步状态state的低16位(w)是否为0
    • 如果w为0,说明其它线程获取了读锁,此时直接进入CLH队列进行阻塞等待(因为读锁与写锁互斥)
    • 如果w不为0,说明有线程获取了写锁,此时要判断是不是当前线程获取了写锁
      • 如果不是,进入CLH队列进行阻塞等待
      • 如果是,就应该判断当前线程获取写锁是否超过最大次数,如果超过,抛出异常,否则更新同步状态state

获取读锁

  1. 一个线程尝试获取读锁时,同样会先判断同步状态state是否为0

    • 如果state为0,说明暂时没有其他线程获取锁,此时需要判断是否需要阻塞

      • 如果需要阻塞,则进入CLH队列进行阻塞等待
      • 如果不需要阻塞,则CAS更新state为读状态
    • 如果state不为0,说明其它线程获取了锁
  2. 当state不为0时,会同步判断同步状态state的低16位
    • 如果存在写锁,直接进入CLH阻塞队列
    • 反之,判断当前线程是否应该被阻塞,如果不应该被阻塞则尝试CAS同步状态,获取成功更新同步锁为读状态

StampedLock

  1. ReentrantReadWriteLock被很好地应用在读多写少的并发场景中,但会存在写线程饥饿的问题

    • Java 8引入StampedLock解决了这个问题
  2. StampedLock不是基于AQS实现的,但实现原理与AQS类似,都是基于队列和锁状态
  3. StampedLock有三种模式:写、悲观读、乐观读,StampedLock在获取锁时会返回一个票据stamp
  4. 一个写线程获取写锁的过程中,首先是通过writeLock获取一个票据stamp(表示锁的版本)
    • WriteLock是一个独占锁,同时只能有一个线程可以获取WriteLock
    • 当一个线程获取WriteLock后,其他请求的线程必须等待
      • 当没有其他线程持有读锁或者写锁时才可以获得WriteLock
  5. 一个读线程获取读锁的过程中,首先会通过tryOptimisticRead获取一个票据stamp
    • 如果当前没有线程持有写锁,会返回一个非0的stamp
    • 然后调用validate验证之前调用tryOptimisticRead返回的stamp在当前是否有其他线程持有了写锁
      • 如果是,那么validate返回0,升级为悲观锁
  6. 相对于ReentrantReadWriteLock,StampedLock获取读锁只使用了与或操作进行校验,不涉及CAS操作
    • 即使第一次乐观锁获取失败,也会马上升级为悲观锁,可以避免一直进行CAS操作而带来的CPU性能消耗问题
  7. 但StampedLock并没有被广泛使用,有几个主要原因
    • StampedLock的功能仅仅只是ReadWriteLock的子集
    • StampedLock不支持重入!!
    • StampedLock的悲观读锁、写锁都不支持条件变量(不符合管程模型)
复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Point {
private double x, y;
private final StampedLock lock = new StampedLock(); public void move(double deltaX, double deltaY) {
// 获取写锁
long stamp = lock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
// 释放写锁
lock.unlockWrite(stamp);
}
} double distanceFromOrigin() {
// 乐观读
long stamp = lock.tryOptimisticRead();
// 拷贝变量
double currentX = x, currentY = y;
// 判断读期间是否有写操作
if (!lock.validate(stamp)) {
// 升级为悲观读
stamp = lock.readLock();
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY + currentY);
}
}

小结

  1. 不管使用synchronized同步锁还是Lock同步锁,只要存在锁竞争就会产生线程阻塞,导致线程频繁切换,增加性能消耗
  2. 优化锁的关键:降低锁竞争
    • synchronized同步锁:减少锁粒度、减少锁占用时间
    • Lock同步锁:锁分离

最后,我是小架

我们下篇文章见!

Java性能 -- Lock优化的更多相关文章

  1. Java性能之优化RPC网络通信

    服务框架的核心 大型服务框架的核心:RPC通信 微服务的核心是远程通信和服务治理 远程通信提供了服务之间通信的桥梁,服务治理提供了服务的后勤保障 服务的拆分增加了通信的成本,因此远程通信很容易成为系统 ...

  2. 推荐:Java性能优化系列集锦

    Java性能问题一直困扰着广大程序员,由于平台复杂性,要定位问题,找出其根源确实很难.随着10多年Java平台的改进以及新出现的多核多处理器,Java软件的性能和扩展性已经今非昔比了.现代JVM持续演 ...

  3. Java基础学习总结(80)——Java性能优化详解

    让Java应用程序运行是一回事,但让他们跑得快就是另外一回事了.在面对对象的环境中,性能问题就像来势凶猛的野兽.但JVM的复杂性将性能调整的复杂程度增加了一个级别.这里Refcard涵盖了JVM in ...

  4. Java 性能优化之 String 篇

    原文:http://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/ Java 性能优化之 String 篇 String 方法用于文本分析 ...

  5. java 性能优化(代码优化)

    参考博文: java 性能优化:35 个小细节,让你提升 java 代码的运行效率

  6. java 性能优化:35 个小细节,让你提升 java 代码的运行效率

    前言 代码 优化 ,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没 ...

  7. 读书笔记系列之java性能优化权威指南 一 第一章

    主题:java性能优化权威指南 pdf 版本:英文版 Java Performance Tuning 忽略:(0~24页)Performance+Acknowledge 1.Strategies, A ...

  8. [原创]Java性能优化权威指南读书思维导图

    [原创]Java性能优化权威指南读书思维导图 书名:Java性能优化权威指南 原书名:Java performance 作者: (美)Charlie Hunt    Binu John 译者: 柳飞 ...

  9. 如何优化 Java 性能?

    对于 Java 性能比较关心的同学大概都知道<Java Performance>这本书,一般而言,很多同学在日常写 Java Code 的时候很少去关心性能问题,但是在我们写 Code 的 ...

随机推荐

  1. 怎么将CAD转PNG格式?这两种方法值得收藏

    在从事相关CAD工作的小伙伴们都知道,CAD中不光需要绘制各种各样的图纸,还需要根据工作的需要来进行图纸格式的转换工作.那有的时候就需要将CAD转换成PNG格式的图片进行使用.那怎么将CAD转PNG格 ...

  2. cookie --中间件

    Cookie简介 cookie是服务器存储在用户计算机中的变量,可以让我们用同一个浏览器访问同一个域名的时共享数据. HTTP是一种无状态协议,简单来说,当你从一个页面,然后跳转到同站点的另一个页面时 ...

  3. ArcGIS Server 10.4切片图的制作与发布

    场景:有一张遥感卫星图,需要以切片图的形式发布 需要的资料:tif的格式遥感图像 发布步骤: 1.选择Service Editor-->Parameters-->Anti-Aliasing ...

  4. ABP入门教程8 - 应用层创建应用服务

    点这里进入ABP入门教程目录 创建目录 在应用层(即JD.CRS.Application)下创建文件夹Course //用以存放Course相关应用服务 在JD.CRS.Application/Cou ...

  5. vue的类element的input类型组件封装

    前提:题主之前只是随意的封装,项目统一ui,然后只实现了父组件拿子组件值,没有实现父组件修改子组件的值,仔细看了文档才知道用model的作用,直接上代码 Vue.component("bas ...

  6. mssql sqlserver 添加表注释和添加列注释的方法分享

     转自: http://www.maomao365.com/?p=8919 摘要: 下文讲述使用sql脚本对数据表或数据列添加注释(备注说明)的方法分享,如下所示: 实验环境:sql server 2 ...

  7. Python 实现 Hangman 小游戏

    Hangman--游戏简介--> 百度百科 打印Hangman def printHangman(N): # 第一行 print("\t____") # 第二行 print( ...

  8. 打包Python文件为exe

    pip install pyinstaller 然后就在终端里执行命令 cd 到目标文件的目录下 执行 pyinstaller  -F  ***.py 即可生成exe

  9. Shadow Map -- 点阴影(全方位)

    昨晚终于把点阴影(深度CubeMap)程序调通了,思想不难,基本就是在上节定向光阴影基础上稍作修改,但是CG程序不太方便Debug,需要输出中间效果图进行判断,耽搁了一会儿. 过程如下: 1.将深度渲 ...

  10. Java Web 学习(5) —— Spring MVC 之数据绑定

    Spring MVC 之数据绑定 数据绑定是将用户输入绑定到领域模型的一种特性. Http 请求传递的数据为 String 类型,通过数据绑定,可以将数据填充为不同类型的对象属性. 基本类型绑定 @R ...