Kevin's blog Kevin's blog
首页
  • AI基础
  • RAG技术
  • 提示词工程
  • Wireshark抓包
  • 常见问题
  • 数据库
  • 代码技巧
  • 浏览器
  • 手册教程
  • 技术应用
  • 流程规范
  • github技巧
  • git笔记
  • vpn笔记
  • 知识概念
  • 学习笔记
  • 环境搭建
  • linux&运维
  • 微服务
  • 经验技巧
  • 实用手册
  • arthas常用
  • spring应用
  • javaAgent技术
  • 网站
友情链接
  • 分类
  • 标签
  • 归档

Kevin

你可以迷茫,但不可以虚度
首页
  • AI基础
  • RAG技术
  • 提示词工程
  • Wireshark抓包
  • 常见问题
  • 数据库
  • 代码技巧
  • 浏览器
  • 手册教程
  • 技术应用
  • 流程规范
  • github技巧
  • git笔记
  • vpn笔记
  • 知识概念
  • 学习笔记
  • 环境搭建
  • linux&运维
  • 微服务
  • 经验技巧
  • 实用手册
  • arthas常用
  • spring应用
  • javaAgent技术
  • 网站
友情链接
  • 分类
  • 标签
  • 归档
  • JVM性能调优

  • 并发编程

  • MySql

  • spring

  • redis

  • zookeeper

  • rabbitMQ

  • 架构

  • 锁

    • java各种锁
    • 分库分表

    • 学习笔记
    • 锁
    kevin
    2024-03-29
    目录

    java各种锁

    # 锁汇总

    转载自:https://github.com/aalansehaiyang/Lock-Learning/blob/master/README.md


    # 👉 乐观锁

    分为三个阶段:数据读取、写入校验、数据写入。

    假设数据一般情况下不会造成冲突,只有在数据进行提交更新时,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回错误信息,让用户决定如何去做。fail-fast机制。

    https://github.com/aalansehaiyang/technology-talk/blob/master/system-architecture/%E9%94%81%E6%9C%BA%E5%88%B6.md

    # 👉 悲观锁

    正如其名,它指对数据被外界(可能是本机的其他事务,也可能是来自其它服务器的事务处理)的修改持保守态度。在整个数据处理过程中,将数据处于锁定状态。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。如果加锁的时间过长,其他用户长时间无法访问,影响程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是长事务而言,这样的开销往往无法承受。

    # 👉 分布式锁

    分布式集群中,对锁接口QPS性能要求很高,单台服务器满足不了要求,可以考虑将锁服务部署在独立的分布式系统中,比如借助分布式缓存来实现。

    • 基于 redis分布式锁 (opens new window)
    • 基于 zookeeper实现的分布式锁 (opens new window)

    # 👉 可重入锁

    可重入锁,也叫做递归锁,是指在同一个线程在调外层方法获取锁的时候,再进入内层方法会自动获取锁。ReentrantLock 和synchronized 都是 可重入锁。可重入锁的一个好处是可一定程度避免死锁。

    http://ifeve.com/java_lock_see4/

    代码示例:$Link {com.lock.reentrant.ReentrantTest}

    # 👉 自旋锁

    自旋锁是采用让当前线程不停地在循环体内执行,当循环的条件被其他线程改变时才能进入临界区。

    自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不断增加时,性能下降明显,因为每个线程都需要执行,会占用CPU时间片。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

    http://ifeve.com/java_lock_see1/

    代码示例:$Link {com.lock.spin.SpinLockTest}

    # 👉 独享锁

    独享锁是指该锁一次只能被一个线程所持有。

    ReentrantLock 、Synchronized 都是独享锁。

    http://blog.csdn.net/ns_code/article/details/17487337

    # 👉 共享锁

    共享锁是指该锁可被多个线程所持有。ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写、写读、写写的过程是互斥的。独享锁与共享锁也是通过AQS(AbstractQueuedSynchronizer)来实现的,通过实现不同的方法,来实现独享或者共享。

    # 👉 互斥锁

    独享锁/共享锁就是一种广义的说法,互斥锁/读写锁指具体的实现。

    互斥锁在Java中的具体实现就是ReentrantLock

    # 👉 读写锁

    读写锁在Java中的具体实现就是ReentrantReadWriteLock

    http://blog.csdn.net/yanyan19880509/article/details/52435135

    http://blog.csdn.net/eson_15/article/details/51553614

    代码示例:$Link {com.lock.readwrite.ReentrantReadWriteLockTest}

    # 👉 阻塞锁

    阻塞锁,可以说是让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。

    JAVA中,能够进入\退出、阻塞状态或包含阻塞锁的方法有 ,synchronized 关键字(其中的重量锁),ReentrantLock,Object.wait()/notify(),LockSupport.park()/unpark()

    http://ifeve.com/java_lock_see3/

    # 👉 公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁

    # 👉 非公平锁

    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。

    可能造成优先级反转或者饥饿现象。对于Java ReentrantLock而言,通过构造函数 ReentrantLock(boolean fair) 指定该锁是否是公平锁,默认是非公平锁。

    非公平锁的优点在于吞吐量比公平锁大。对于Synchronized而言,也是一种非公平锁。

    # 👉 分段锁

    分段锁其实是一种锁的设计,目的是细化锁的粒度,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7 中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

    当需要put元素的时候,并不是对整个HashMap加锁,而是先通过hashcode知道要放在哪一个分段中,然后对这个分段加锁,所以当多线程put时,只要不是放在同一个分段中,可支持并行插入。

    # 👉 对象锁

    一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。

    在java程序中,只需要使用synchronized块或者synchronized方法就可以标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。

    synchronized修饰非静态方法、同步代码块的synchronized (this)、synchronized (非this对象),锁的是对象,线程想要执行对应同步代码,需要获得对象锁。

    http://blog.csdn.net/u013142781/article/details/51697672

    代码示例:$Link {com.lock.object.SynchronizedMethod}

    代码示例:$Link {com.lock.object.SynchronizedThis}

    代码示例:$Link {com.lock.object.SynchronizedObject}

    # 👉 类锁

    synchronized修饰静态方法或者同步代码块的synchronized (类.class),线程想要执行对应同步代码,需要获得类锁。

    代码示例:$Link {com.lock.class1.SynchronizedStaticMethod}

    代码示例:$Link {com.lock.class1.SynchronizedClass}

    # 👉 信号量

    Semaphore是用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。

    如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量。

    代码示例:$Link {com.lock.semaphore.SemaphoreTest}

    # 👉 条件变量Condition

    条件变量很大一个程度上是为了解决Object.wait/notify/notifyAll难以使用的问题。

    有人会问,如果一个线程lock() 后被挂起还没有执行unlock(),那么另外一个线程就拿不到锁,那么就无法唤醒前一个线程signal(),这样岂不是“死锁”了?

    解释:
    进入lock.lock()后唯一可能释放锁的操作就是await()。也就是说await()操作实际上就是释放锁,然后挂起线程,一旦条件满足就被唤醒,再次获取锁!
    
    1
    2

    http://blog.csdn.net/vernonzheng/article/details/8288251

    代码示例:$Link {com.lock.condition.ConditionDemo}

    # 👉 行级锁

    行级锁是数据库引擎中对记录更新的时候引擎本身上的锁,是数据库引擎的一部分,在数据库引擎更新一条数据的时候,本身就会对记录上锁,这时候即使有多个请求更新,也不会产生脏数据,行级锁的粒度非常细,上锁的时间窗口也最少,只有更新数据记录的那一刻,才会对记录上锁,因此,能大大减少数据库操作的冲突,发生锁冲突的概率最低,并发度也最高。

    通常在扣减库存的场景下使用行级锁,这样可以通过数据库引擎本身对记录加锁的控制,保证数据库的更新的安全性,并且通过where语句的条件,保证库存不会被减到0以下,也就是能够有效的控制超卖的场景。

    update ... set amount = amount - 1 where id = $id and amount - 1 >=0 
    
    1

    另外一种场景是在状态转换的时候使用行级锁,例如交易引擎中,状态只能从init流转到doing状态,任何重复的从init到doing的流转,或者从init到finished等其他状态的流转都会失败。

    boolean result = executeSql("update ... set status = 'doing' where id = $id and status = 'init'"); 
    if (result) 
    	{   // process sucessful logic } 
    else 
    	{   // process failure logic }
    
    1
    2
    3
    4
    5

    行级锁的并发性较高,性能是最好的,适用于高并发下扣减库存和控制状态流转的方向的场景。

    但是,有人说这种方法是不能保证幂等的,比如说,在扣减余额场景,多次提交可能会扣减多次,这确实是实际存在的,但是,我们是有应对方案的,我们可以记录扣减的操作日志,如果有非幂等的场景出现,通过比较操作记录。

    boolean result = executeSql("update ... set amount = amount - 1 where id = $id and amount > 1"); 
    if (result) 
    	{    executeSql("insert into hist (pre_amout, post_amout) values ($amount, $amount -1)");    
    	// process successful logic 
    	} else {
    	    // process failure logic 
    }
    
    1
    2
    3
    4
    5
    6
    7

    在支付平台架构设计中,通常对交易和支付系统的流水表的状态流转的控制、对账户系统的状态控制,分账和退款余额的更新等,都推荐使用行级锁,而单独使用乐观锁和悲观锁是不推荐的。

    # 👉 其它

    Synchronized:非公平,悲观,独享,互斥,可重入的重量级锁
    ReentrantLock:默认非公平但可实现公平的(构造器传true),悲观,独享,互斥,可重入,重量级锁。
    ReentrantReadWriteLocK:默认非公平但可实现公平的(构造器传true),悲观,写独享,读共享,读写,可重入,重量级锁。
    
    1
    2
    3

    线程A和B都要获取对象o的锁定,假设A获取了对象o锁,B将等待A释放对o的锁定

    • synchronized ,如果A不释放,B将一直等下去,不能被中断
    • ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

    ReentrantLock获取锁定有三种方式:

    • lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁

    • tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false

    • tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

    • lockInterruptibly(),如果获取了锁定立即返回,如果没有获取锁,当前线程处于休眠状态,直到获取锁定,或者当前线程被别的线程中断

    • Java 各种锁的小结 (opens new window)

    上次更新: 2024/04/01, 19:14:44
    cdc技术
    为何需要分库分表

    ← cdc技术 为何需要分库分表→

    最近更新
    01
    AI是如何学习的
    06-05
    02
    chatGpt提示原则
    06-05
    03
    提示词工程实践指南
    06-05
    更多文章>
    | Copyright © 2022-2025 Kevin | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式