synchronized / Lock / CAS

  1. synchronized和Lock实现的同步锁机制,都属于悲观锁,而CAS属于乐观锁
  2. 悲观锁在高并发的场景下,激烈的锁竞争会造成线程阻塞,而大量阻塞线程会导致系统的上下文切换,增加系统的性能开销

乐观锁

  1. 乐观锁:在操作共享资源时,总是抱着乐观的态度进行,认为自己能够完成操作
  2. 但实际上,当多个线程同时操作一个共享资源时,只有一个线程会成功,失败的线程不会被挂起,仅仅只是返回
  3. 乐观锁相比于悲观锁来说,不会带来死锁、饥饿等活性故障问题,线程间的相互影响也远远比悲观锁要小
    • 乐观锁没有因竞争而造成的系统上下文切换,所以在性能上更胜一筹

实现原理

  1. CAS是实现乐观锁的核心算法,包含3个参数:V(需要更新的变量),E(预期值)、N(最新值)
  2. 只有V等于E时,V才会被设置为N
  3. 如果V不等于E了,说明其它线程已经更新了V,此时该线程不做操作,返回V的真实值

CAS实现原子操作

AtomicInteger是基于CAS实现的一个线程安全的整型类,Unsafe调用CPU底层指令实现原子操作

复制

1
2
3
4
5
6
7
8
// java.util.concurrent.atomic.AtomicInteger
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
} public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
复制

1
2
3
4
5
6
7
8
9
10
11
12
// sun.misc.Unsafe
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
} public native int getIntVolatile(Object o, long offset); public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

处理器实现原子操作

  1. CAS是调用处理器底层指令来实现原子操作的
  2. 处理器和物理内存之间的通信速度要远低于处理器间的处理速度,所以处理器有自己的内部缓存(L1/L2/L3)
  3. 服务器通常为多处理器,并且处理器是多核的,每个处理器维护了一块字节的缓存存,每个内核也维护了一块字节的缓存
    • 此时在多线程并发就会存在缓存不一致的问题,从而导致数据不一致
  4. 处理器提供了总线锁定和缓存锁定两种机制来保证复杂内存操作的原子性
    • 总线锁定

      • 当处理器要操作一个共享变量时,会在总线上会发出一个Lock信号,此时其它处理器就不能操作共享变量了
      • 总线锁定在阻塞其他处理器获取该共享变量的操作请求时,也可能会导致大量阻塞,从而增加系统的性能开销
    • 缓存锁定(后来出现)
      • 当某个处理器对缓存中的共享变量进行了操作,就会通知其他处理器放弃存储或者重新读取该共享变量
      • 目前最新的处理器都支持缓存锁定机制

优化CAS乐观锁

  1. 乐观锁在并发性能上要优于悲观锁

    • 但在写大于读的操作场景下,CAS失败的可能性增大,如果循环CAS,会长时间占用CPU
    • 例如上面的AtomicInteger#getAndIncrement
  2. JDK 1.8中,提供了新的原子类LongAdder
    • LongAdder在高并发场景下会比AtomicInteger和AtomicLong的性能更好,代价是消耗更多的内存空间

      • 核心思想:空间换时间
      • 实现原理:降低操作共享变量的并发数
    • LongAdder内部由一个base变量和一个cell[]数组组成
      • 当只有一个写线程(没有竞争)

        • LongAdder会直接使用base变量作为原子操作变量,通过CAS操作修改base变量
      • 当有多个写线程(存在竞争)
        • 除了占用base变量的一个写线程外,其他写线程的value值会分散到cell数组中
        • 不同线程会命中到数组的不同槽中,各个线程只对自己槽中的value进行CAS操作
        • value=base+∑ni=0Cell[i]value=base+∑i=0nCell[i]
    • LongAdder在操作后的返回值只是一个近似准确的值,但最终返回的是一个准确的值
      • LongAdder不适合实时性要求较高的场景

性能对比

  1. 读大于写,读写锁ReentrantReadWriteLock、读写锁StampedLock、乐观锁LongAdder的性能最好
  2. 写大于读,乐观锁的性能最好,其他四种锁的性能差不多
  3. 读约等于写,两种读写锁和乐观锁的性能要优于synchronized和Lock

小结

  1. 乐观锁的常见使用场景:数据库更新

    • 为每条数据定义一个版本号,在更新前获取版本号,在更新数据时,再判断版本号是否被更新过,如果没有才更新数据
  2. CAS乐观锁的使用比较受限,因为乐观锁只能保证单个变量操作的原子性
  3. CAS乐观锁在高并发写大于读的场景下
    • 大部分线程的原子操作会失败,失败后的线程将不断重试CAS原子操作,导致大量线程长时间占用CPU资源
    • JDK 1.8中,新增了原子类LongAdder,采用空间换时间的思路解决了这个问题,但实时性不高

Java性能 -- CAS乐观锁的更多相关文章

  1. Java:CAS(乐观锁)

    本文讲解CAS机制,主要是因为最近准备面试题,发现这个问题在面试中出现的频率非常的高,因此把自己学习过程中的一些理解记录下来,希望能对大家也有帮助. 什么是悲观锁.乐观锁?在java语言里,总有一些名 ...

  2. CAS(乐观锁)以及ABA问题

    https://blog.csdn.net/wwd0501/article/details/88663621独占锁是一种悲观锁,synchronized就是一种独占锁:它假设最坏的情况,并且只有在确保 ...

  3. Java并发:乐观锁

    作者:汤圆 个人博客:javalover.cc 简介 悲观锁和乐观锁都属于比较抽象的概念: 我们可以用拟人的手法来想象一下: 悲观锁:像有些人,凡事都往坏的想,做最坏的打算:在java中就表现为,总是 ...

  4. Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

    首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...

  5. JAVA多线程学习四 - CAS(乐观锁)

    本文讲解CAS机制,主要是因为最近准备面试题,发现这个问题在面试中出现的频率非常的高,因此把自己学习过程中的一些理解记录下来,希望能对大家也有帮助. 什么是悲观锁.乐观锁?在java语言里,总有一些名 ...

  6. [数据库锁机制] 深入理解乐观锁、悲观锁以及CAS乐观锁的实现机制原理分析

    前言: 在并发访问情况下,可能会出现脏读.不可重复读和幻读等读现象,为了应对这些问题,主流数据库都提供了锁机制,并引入了事务隔离级别的概念.数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务 ...

  7. Java中的乐观锁

    1.前言 之前好几次看到有人在面经中提到了乐观锁与悲观锁,但是一本<Java Concurrency In Practice>快看完了都没有见到过这两种锁,今天终于在第15章发现了它们的踪 ...

  8. Java多线程:乐观锁、悲观锁、自旋锁

    悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁.传统的关系型数据 ...

  9. Java性能之synchronized锁的优化

    synchronized / Lock 1.JDK 1.5之前,Java通过synchronized关键字来实现锁功能 synchronized是JVM实现的内置锁,锁的获取和释放都是由JVM隐式实现 ...

随机推荐

  1. xwiki 知识管理系统

    搭建一个知识管理平台, 用于知识库管理/规范管理, 可以作wiki, 可以将word/excel等导入进去, 支持全文搜索, 可以记周报, 会议纪要. 现在有很多文档管理系统, 比如阿里的语雀.腾讯的 ...

  2. [b0037] python 归纳 (二二)_多进程数据共享和同步_管道Pipe

    # -*- coding: utf-8 -*- """ 多进程数据共享 管道Pipe 逻辑: 2个进程,各自发送数据到管道,对方从管道中取到数据 总结: 1.只适合两个进 ...

  3. requests---重定向

    通常我们抓包的过程中,都会看到302的状态码,那么这个过程发生了什么? 什么是重定向 就是通过各种方法将各种网络请求重新定个方向转到其它位置,本来应该从a出发到达b但是最终到达了c,这种场景就叫做重定 ...

  4. [转]5 种使用 Python 代码轻松实现数据可视化的方法

    数据可视化是数据科学家工作中的重要组成部分.在项目的早期阶段,你通常会进行探索性数据分析(Exploratory Data Analysis,EDA)以获取对数据的一些理解.创建可视化方法确实有助于使 ...

  5. 201871010111-刘佳华《面向对象程序设计(java)》第七周学习总结

    201871010111-刘佳华<面向对象程序设计(java)>第七周学习总结 实验时间 2019-10-11 1.实验目的与要求 1) 掌握四种访问权限修饰符的使用特点: (1)进一步理 ...

  6. 鲜贝7.3--postman安装

    Postman电脑客户端安装: Postman的安装非常简单,在windows系统只需要双击安装包,然后什么都不需要操作,它直接就自己完成了,如下图.如果是mac 也是跟普通软件的安装方法相同.在初次 ...

  7. 剑指Offer-24.二叉树中和为某一值的路径(C++/Java)

    题目: 输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径.路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径.(注意: 在返回值的list中,数组长度大的 ...

  8. 浅谈字符串Hash

    浅谈字符串Hash 本篇随笔讲解Hash(散列表)的一个重要应用:字符串Hash. 关于Hash Hash是一种数据结构,叫做Hash表(哈希表),也叫散列表.关于Hash的实现,其实与离散化颇为类似 ...

  9. pandas-缺失值处理

    import pandas as pd import numpy as np Step 1.加载数据集 # header=0以第一行作为列名 tip = pd.read_csv("lianx ...

  10. CentOS单机安装FastDFS&整合Nginx

    单机安装 一 准备工作 准备linux服务器或虚拟机,这里是虚拟机,操作系统CentOS 6.4 Tracker 和 Storage 安装在一台机器上 FastDFS 5.08版本 1,准备软件 软件 ...