Java性能 -- Lock优化
Lock / synchronized
Lock锁的基本操作是通过乐观锁实现的,由于Lock锁也会在阻塞时被挂起,依然属于悲观锁
| synchronized | Lock | |
|---|---|---|
| 实现方式 | JVM层实现 | Java底层代码实现 |
| 锁的获取 | JVM隐式获取 | lock() / tryLock() / tryLock(timeout, unit) / lockInterruptibly() |
| 锁的释放 | JVM隐式释放 | unlock() |
| 锁的类型 | 非公平锁、可重入 | 非公平锁/公平锁、可重入 |
| 锁的状态 | 不可中断 | 可中断 |
| 锁的性能 | 高并发下会升级为重量级锁 | 更稳定 |
实现原理
- Lock锁是基于Java实现的锁,Lock是一个接口
- 常见的实现类:ReentrantLock、ReentrantReadWriteLock,都是依赖AbstractQueuedSynchronizer(AQS)实现
- AQS中包含了一个基于链表实现的等待队列(即CLH队列),用于存储所有阻塞的线程
- AQS中有一个state变量,该变量对ReentrantLock来说表示加锁状态
- AQS中的CLH队列的所有操作均通过CAS操作实现的

锁分离优化
ReentrantReadWriteLock
- ReentrantLock是一个独占锁,同一时间只允许一个线程访问
- ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问
- ReentrantReadWriteLock内部维护了两把锁,一把用于读操作的ReadLock,一把用于写操作的WriteLock
- ReentrantReadWriteLock如何保证共享资源的原子性?ReentrantReadWriteLock也是基于AQS实现的
- 自定义同步器(继承AQS)需要在同步状态state上维护多个读线程和一个写线程的状态
- ReentrantReadWriteLock利用了高低位,来实现一个整型控制两种状态的功能
- 将同步状态state切分为两部分,高16位表示读,低16位表示写
获取写锁
- 一个线程尝试获取写锁时,会先判断同步状态state是否为0
- 如果state为0,说明暂时没有其他线程获取锁
- 如果state不为0,说明其它线程获取了锁
- 当state不为0时,会再去判断同步状态state的低16位(w)是否为0
- 如果w为0,说明其它线程获取了读锁,此时直接进入CLH队列进行阻塞等待(因为读锁与写锁互斥)
- 如果w不为0,说明有线程获取了写锁,此时要判断是不是当前线程获取了写锁
- 如果不是,进入CLH队列进行阻塞等待
- 如果是,就应该判断当前线程获取写锁是否超过最大次数,如果超过,抛出异常,否则更新同步状态state

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

StampedLock
- ReentrantReadWriteLock被很好地应用在读多写少的并发场景中,但会存在写线程饥饿的问题
- Java 8引入StampedLock解决了这个问题
- StampedLock不是基于AQS实现的,但实现原理与AQS类似,都是基于队列和锁状态
- StampedLock有三种模式:写、悲观读、乐观读,StampedLock在获取锁时会返回一个票据stamp
- 一个写线程获取写锁的过程中,首先是通过writeLock获取一个票据stamp(表示锁的版本)
- WriteLock是一个独占锁,同时只能有一个线程可以获取WriteLock
- 当一个线程获取WriteLock后,其他请求的线程必须等待
- 当没有其他线程持有读锁或者写锁时才可以获得WriteLock
- 一个读线程获取读锁的过程中,首先会通过tryOptimisticRead获取一个票据stamp
- 如果当前没有线程持有写锁,会返回一个非0的stamp
- 然后调用validate验证之前调用tryOptimisticRead返回的stamp在当前是否有其他线程持有了写锁
- 如果是,那么validate返回0,升级为悲观锁
- 相对于ReentrantReadWriteLock,StampedLock获取读锁只使用了与或操作进行校验,不涉及CAS操作
- 即使第一次乐观锁获取失败,也会马上升级为悲观锁,可以避免一直进行CAS操作而带来的CPU性能消耗问题
- 但StampedLock并没有被广泛使用,有几个主要原因
- StampedLock的功能仅仅只是ReadWriteLock的子集
- StampedLock不支持重入!!
- StampedLock的悲观读锁、写锁都不支持条件变量(不符合管程模型)
1 |
public class Point {
|
小结
- 不管使用synchronized同步锁还是Lock同步锁,只要存在锁竞争就会产生线程阻塞,导致线程频繁切换,增加性能消耗
- 优化锁的关键:降低锁竞争
- synchronized同步锁:减少锁粒度、减少锁占用时间
- Lock同步锁:锁分离
最后,我是小架
我们下篇文章见!
Java性能 -- Lock优化的更多相关文章
- Java性能之优化RPC网络通信
服务框架的核心 大型服务框架的核心:RPC通信 微服务的核心是远程通信和服务治理 远程通信提供了服务之间通信的桥梁,服务治理提供了服务的后勤保障 服务的拆分增加了通信的成本,因此远程通信很容易成为系统 ...
- 推荐:Java性能优化系列集锦
Java性能问题一直困扰着广大程序员,由于平台复杂性,要定位问题,找出其根源确实很难.随着10多年Java平台的改进以及新出现的多核多处理器,Java软件的性能和扩展性已经今非昔比了.现代JVM持续演 ...
- Java基础学习总结(80)——Java性能优化详解
让Java应用程序运行是一回事,但让他们跑得快就是另外一回事了.在面对对象的环境中,性能问题就像来势凶猛的野兽.但JVM的复杂性将性能调整的复杂程度增加了一个级别.这里Refcard涵盖了JVM in ...
- Java 性能优化之 String 篇
原文:http://www.ibm.com/developerworks/cn/java/j-lo-optmizestring/ Java 性能优化之 String 篇 String 方法用于文本分析 ...
- java 性能优化(代码优化)
参考博文: java 性能优化:35 个小细节,让你提升 java 代码的运行效率
- java 性能优化:35 个小细节,让你提升 java 代码的运行效率
前言 代码 优化 ,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没 ...
- 读书笔记系列之java性能优化权威指南 一 第一章
主题:java性能优化权威指南 pdf 版本:英文版 Java Performance Tuning 忽略:(0~24页)Performance+Acknowledge 1.Strategies, A ...
- [原创]Java性能优化权威指南读书思维导图
[原创]Java性能优化权威指南读书思维导图 书名:Java性能优化权威指南 原书名:Java performance 作者: (美)Charlie Hunt Binu John 译者: 柳飞 ...
- 如何优化 Java 性能?
对于 Java 性能比较关心的同学大概都知道<Java Performance>这本书,一般而言,很多同学在日常写 Java Code 的时候很少去关心性能问题,但是在我们写 Code 的 ...
随机推荐
- JVM内存模型与类加载机制
一. java虚拟机的内存模型如图: 补习一下jvm内存模型中的各个组成部分 堆: 我们new出来的对象全部放在堆中,他是jvm所能够动态分配的最大的一块空间 优点: 内存动态分配,生命周期不必事先告 ...
- QT使用QPainter加水印
QT使用QPainter加水印 加水印的代码 //为QPixmap添加水印 void MainWindow::addMask(QPixmap& pm, const QString& t ...
- Mysql高手系列,连载中
Mysql系列的目标是:通过这个系列从入门到全面掌握一个高级开发所需要的全部技能. 欢迎大家加我微信itsoku一起交流java.算法.数据库相关技术. 第1篇:mysql基础知识 第2篇:详解mys ...
- Java注解简单学习
注解(也被称作元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们在稍后某个时刻可以很方便的使用这些数据,其在一定程度上将元数据与源代码文件结合在一起,而不是保存在外部文档中. 注解使我们可以 ...
- Add an Editor to a Detail View 将编辑器添加到详细信息视图
In this lesson, you will learn how to add an editor to a Detail View. For this purpose, the Departme ...
- ubuntu18.10 上安装docker容器
网上有的安装步骤太复杂,并且安装过程中容易出错,其它安装不难,只需一条命令即可. 安装成功后,使用命令查看docker状态 systemctl status docker 安装前更新下包源 sudo ...
- vue发送ajax请求
一.vue-resource 1.简介 一款vue插件,用于处理ajax请求,vue1.x时广泛应用,现不被维护. 2.使用流程 step1:安装 [命令行输入] npm install vue-re ...
- Dynamics CRM - js中用webapi基于fetchxml查询遇到的问题 -- Invalid URI: The Uri scheme is too long.
最近用WebApi做基于Fetchxml的查询的时候,遇到一个很蛋疼的报错:Invalid URI: The Uri scheme is too long. 检查了整个URL,也没发现有什么问题. - ...
- OC代码规范小记
代码规范 一:基本代码命名 1.通用原则 尽量清晰又简洁,无法两全时清晰更重要,可读性优先级更高. insertObject:atIndex: 好的 insert:at 坏的 removeObject ...
- angularjs路由传值$routeParams
AngularJS利用路由传值, 1.导包 <script src="angular.min.js"></script> <script src=&q ...