• 面试(乐观锁和悲观锁)


    一、解决了什么问题

    1.1、并发的优点和缺点

    1.1.1、优点

    1、并发是指在同一时间内同时执行多个线程的能力,从而提高了程序的性能和响应速度。

    2、Java中的线程模型是基于共享内存的并发模型,多个线程共享同一块内存空间,它们可以通过读写共享变量来实现线程间的通信和交互

    1.1.2、缺点

    线程安全问题:是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

    1.2、解决方案

    锁是一种同步机制,可以确保多个线程之间共享资源的互斥访问,从而避免出现数据竞争和线程安全问题。使用锁的主要目的是保证代码的正确性和可靠性。

    二、锁机制

    乐观锁(Optimistic Locking)和悲观锁(Pessimistic Locking)是在并发编程中常用的两种锁机制,用于解决多线程并发访问共享资源时的数据一致性问题。

    2.1、乐观锁

    1、乐观锁的基本思想是假设并发访问不会导致冲突;

    2、因此在读取数据时不加锁,只在更新数据时进行检查。

    乐观锁通常使用版本号或时间戳来实现。当一个线程要更新数据时,会先读取数据的版本号或时间戳,并将其保存下来。在提交更新时,会再次读取数据的版本号或时间戳进行比较,如果两次读取的值相同,则更新成功;如果不同,则说明数据被其他线程修改过,更新失败,需要进行相应的处理(如重试或回滚)。

    2.2、悲观锁

    悲观锁的基本思想是假设并发访问会导致冲突;

    因此在读取和更新数据时都会加上锁,避免其他线程修改数据。

    悲观锁通常使用排他锁(如独占锁)来实现,即一个线程获取到锁后,其他线程无法访问该资源,直到锁被释放。悲观锁的使用场景通常是对于更新操作频繁的情况,因为它会导致其他线程的阻塞和等待。

    乐观锁和悲观锁各有其适用的场景:

    1、乐观锁适用于多读少写的场景,假设并发冲突的概率较小,可以提高并发性能。当发生冲突时,需要进行相应的处理,如重试。

    2、悲观锁适用于多写少读的场景,假设并发冲突的概率较大,需要确保数据的一致性。悲观锁的主要缺点是会引起线程的阻塞和等待,影响并发性能。

    在实际应用中,选择使用乐观锁还是悲观锁需要根据具体的业务需求和并发场景进行评估和选择。

    三、死锁问题

    3.1、什么是死锁

    假设有两个线程A 和 B,两把锁 LockA和LockB,线程A持有LockA锁,线程B持有LockB锁 

    3.2、如何排查?

    1、找到Java进程id

    ps -ef | grep java

    jps -l

    2、查看阻塞态线程堆栈

    jstack -l pid | grep DEADLOCK -A100

    3、线程id转化16进制

    printf ‘0x%x’  tid

    4、找到线程堆栈

    jstack pid | grep tid

    3.3、如何避免产生死锁

    3.3.1、产生死锁的四个条件

    1、互斥,也就是多个线程在同一时间使用的不是同一个资源。

    2、持有并等待,持有当前的锁,并等待获取另一把锁

    3、不可剥夺,当前持有的锁不会被释放

    4、环路等待,就是两个线程互相尝试获取对方持有的锁,并且当前自己持有的锁不会释放。

    3.3.2、解决方案

    我们只需要让其中一个条件不成立,那么就可以避免死锁问题的产生。一般最常见的解决方式就是使用资源有序分配法,来使环路等待条件不成立。

    3.3.2.1、资源有序分配

    可以通过对锁的获取顺序进行统一,降低死锁的概率。

    3.3.2.2、使用定时锁

    即在获取锁的时候设置超时时间,如果超时则放弃获取,避免长时间等待。

    3.3.2.3、尽量减小锁的作用域

    尽量减小锁的作用域,即只在必要的部分进行加锁,这样可以减少锁的竞争,降低死锁的概率。

    四、场景分析

    4.1、操作系统

    1、悲观锁:互斥锁

    2、乐观锁:自旋锁

    4.2、JAVA

    4.2.1、synchronized锁

    synchronized是Java中最基本的锁机制。它是通过在方法或代码块前加上关键字synchronized来实现的。当线程进入synchronized方法或代码块时,会自动获得锁,并在执行完毕后释放锁。synchronized锁是可重入的,也就是说一个线程可以多次获得同一把锁。

    4.2.2、CAS

    4.2.2.1、ReentrantLock锁:

    ReentrantLock是Java.util.concurrent包中提供的一种可重入锁。它相比于synchronized锁更加灵活,提供了更多的高级特性。使用ReentrantLock锁需要手动获取和释放锁,可以通过lock()方法获取锁,通过unlock()方法释放锁。

    4.2.2.2、ReadWriteLock锁:

    ReadWriteLock是Java.util.concurrent包中提供的一种读写锁。它允许多个线程同时读共享资源,但只允许一个线程写共享资源。具体来说,当一个线程获得了写锁,其他线程无法同时获得读锁或写锁;当一个线程获得了读锁,其他线程可以同时获得读锁,但不能获得写锁。

    4.2.2.3、LockSupport锁:

    LockSupport是Java.util.concurrent包中提供的一种线程阻塞工具。它可以在线程内部对线程进行阻塞和唤醒操作,而不需要获取和释放锁。LockSupport通过park()方法阻塞线程,通过unpark()方法唤醒被阻塞的线程。

    4.2.2.4、AtomicInteger锁:

    AtomicInteger是Java.util.concurrent.atomic包中提供的一个线程安全的整数类。它通过原子操作来保证多线程环境下对整数的操作的原子性。AtomicInteger可以替代synchronized关键字,实现线程安全的自增、自减、比较等操作。

  • 相关阅读:
    记一次 .NET某游戏币自助机后端 内存暴涨分析
    【光流估计】——gmflow中self attention,cross attention的比较
    信息安全和网络空间安全选哪个?
    消防产品在酒店行业的应用
    vite react react-pdf pdfjs-dist 加载不全的解决方案 cmaps本地路径
    LintCode 1394 · Goat Latin (字符串处理题)
    成为会带团队的技术人 技术债务:如何带领团队从困境中突围而出?
    el-form表单动态校验(场景: 输入框根据单选项来动态校验表单 没有选中的选项就不用校验)
    指针和数组试题解析(4)字符数组部分续集
    【程序员面试金典】01.05. 一次编辑
  • 原文地址:https://blog.csdn.net/qq_18871751/article/details/132797069