Java并发编程之锁的艺术:面试与实战指南(一)

Java并发编程之锁的艺术:面试与实战指南(一)

文章目录

  • Java并发编程之锁的艺术:面试与实战指南(一)
    • 一、什么是锁?
    • 二、Java中有哪些类型的锁?
    • 三、synchronized 和 ReentrantLock的区别是什么?
    • 四、什么是乐观锁和悲观锁?
      • 悲观锁(Pessimistic Locking)
      • 乐观锁(Optimistic Locking)
    • 五、死锁是什么?如何避免?
    • 六、如何在Java中实现一个自定义的锁?
    • 七、什么是可重入锁(ReentrantLock)?
    • 八、什么是公平锁和非公平锁?

🌈你好呀!我是 山顶风景独好
💝欢迎来到我的博客,很高兴能够在这里和您见面!
💝希望您在这里可以感受到一份轻松愉快的氛围!
💝不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
🚀 欢迎一起踏上探险之旅,挖掘无限可能,共同成长!

一、什么是锁?

锁(Lock)是一种同步机制,用于控制多个线程对共享资源的访问。当一个线程需要访问某个共享资源时,它必须先获取该资源的锁,以确保在访问过程中其他线程不会同时访问该资源,从而避免数据的不一致性和其他并发问题。

二、Java中有哪些类型的锁?

  1. 乐观锁(Optimistic Locking):

    • 乐观锁认为一个线程去拿数据的时候不会有其他线程对数据进行更改,所以不会立即上锁。它会在数据更新时进行检查,如果数据在此期间没有被其他线程修改过,则更新成功;否则,操作失败。
    • 实现方式包括 CAS(Compare and Swap)机制、版本号 机制等。
  2. 悲观锁(Pessimistic Locking):

    • 悲观锁认为一个线程去拿数据时一定会有其他线程对数据进行更改,所以一个线程在拿数据的时候都会顺便加锁,这样别的线程此时想拿这个数据就会阻塞。
    • Java中的 synchronized关键字Lock的实现类 都是悲观锁的例子。
  3. 自旋锁(Spinlock):

    • 当一个线程尝试获取某个锁时,如果该锁已经被其他线程持有,则该线程不会立即阻塞,而是会采用循环的方式去尝试获取锁,直到获取到锁或者超过设定的最大循环次数。
    • 自旋锁适用于锁被持有的时间较短,且线程切换的开销较大的场景。
  4. 适应性自旋锁(Adaptive Spinlock):

    • 适应性自旋锁是自旋锁的一种优化形式。在获取锁的过程中,它会根据前一次获取锁的成功与否以及上一次自旋等待的时间等因素,动态地调整本次自旋等待的时间。
  5. 锁升级(Lock Escalation):

    • Java中的synchronized关键字在JVM层面进行了优化,包括锁升级机制。锁升级是指从一种锁状态逐渐过渡到另一种锁状态的过程,例如从偏向锁轻量级锁逐渐过渡到重量级锁
  6. 公平锁(Fair Lock)与非公平锁(Non-fair Lock):

    • 公平锁表示线程按照申请锁的顺序来获取锁,即等待时间最长的线程将优先获取锁。
    • 非公平锁则不保证等待时间最长的线程会先获得锁,有可能后申请的线程比先申请的线程优先获取锁。Java中的ReentrantLock可以通过构造函数指定是否为公平锁,默认是非公平锁。
  7. 可重入锁(Reentrant Lock):

    • 可重入锁允许同一个线程多次获取同一把锁而不会造成死锁。Java中的ReentrantLock就是可重入锁的一个实现。
  8. 独享锁(Exclusive Lock)与共享锁(Shared Lock):

    • 独享锁又称排它锁,同一时间只允许一个线程获取该锁,其他线程必须等待锁释放后才能获取。
    • 共享锁又称读锁,允许多个线程同时获取该锁进行读操作,但在读锁被释放之前,不允许其他线程进行写操作。
  9. 互斥锁(Mutex)与读写锁(Read-Write Lock):

    • 互斥锁是一种最简单的独享锁,同一时间只允许一个线程访问被保护的资源。
    • 读写锁则是对互斥锁的扩展,允许多个线程同时读取被保护的资源,但在读取过程中不允许其他线程进行写操作;而在写操作时,则不允许其他线程进行读或写操作。
  10. 分段锁(Segment Lock):

    • 分段锁是一种锁的设计思想,并不是具体的一种锁。它将一个大的数据结构(如数组或哈希表)分成多个小的段(Segment),每个段都有自己的锁。这样,多个线程可以并行地访问不同的段,从而提高了并发性能。例如,Java中的ConcurrentHashMap就采用了分段锁的设计。

三、synchronized 和 ReentrantLock的区别是什么?

  1. 获取锁的方式:

    • synchronized:是隐式锁,它在进入同步代码块或方法时自动获取锁,退出时自动释放锁。开发者无需显式地调用任何方法来获取或释放锁。
    • ReentrantLock:是显式锁,需要手动调用lock()方法获取锁,并在finally块中调用unlock()方法释放锁。这种方式提供了更大的灵活性,但也增加了出错的可能性,因为开发者必须确保在finally块中释放锁。
  2. 锁的公平性:

    • synchronized:是非公平锁,它并不保证等待时间最长的线程会先获得锁。
    • ReentrantLock:默认情况下也是非公平锁,但可以通过构造函数设置为公平锁。在公平锁的情况下,等待时间最长的线程会先获得锁。
  3. 功能丰富性:

    • synchronized:是Java内置的关键字,其功能相对较为简单,主要用于实现线程同步。
    • ReentrantLock:提供了比synchronized更丰富的功能。例如,它可以设置获取锁的超时时间,可以判断锁是否被其他线程持有,以及可以使用Condition类实现线程等待/通知机制等。
  4. 可重入性:

    • synchronized:是可重入的,这意味着一个线程可以多次获取同一把锁,而不会造成死锁。
    • ReentrantLock:同样是可重入的,与synchronized在这方面具有相同的行为。
  5. 中断响应:

    • synchronized:在获取锁的过程中,如果线程被中断,它会抛出InterruptedException异常,但不会释放锁。这可能导致死锁,因为其他等待锁的线程将无法获取锁。
    • ReentrantLock:提供了更灵活的中断响应。如果线程在等待锁的过程中被中断,它可以决定是继续等待、放弃等待还是响应中断。
  6. 性能:

    • 在高并发的情况下,ReentrantLock的性能可能会优于synchronized。但需要注意的是,synchronized的优化已经足够好,在许多场景下,其性能与ReentrantLock相当甚至更好。

四、什么是乐观锁和悲观锁?

悲观锁(Pessimistic Locking)

悲观锁认为并发操作之间发生冲突的可能性很高,因此,在数据被处理时,它会锁定资源以确保数据在处理过程中不会被其他事务修改。

  • 特点

    1. 悲观锁在数据被修改前就已经加锁,数据在被处理的过程中不会被其他事务读取或修改。
    2. 悲观锁的实现依赖于数据库提供的锁机制,如行锁、表锁等。
    3. 悲观锁适用于写操作频繁的场景,因为它可以避免脏读、不可重复读和幻读等并发问题。
  • 示例

    • 在SQL中,使用SELECT … FOR UPDATE语句可以对选定的行进行加锁,以确保在事务完成之前这些行不会被其他事务修改。
    • 在Java中,synchronized关键字和ReentrantLock等锁机制可以视为悲观锁的实现,因为它们会阻塞其他尝试访问共享资源的线程。

乐观锁(Optimistic Locking)

乐观锁认为并发操作之间发生冲突的可能性很小,因此它不会立即锁定资源,而是在数据提交更新时,检查数据是否被其他事务修改过。

  • 特点

    1. 乐观锁在数据被读取时不会加锁,而是在数据更新时检查版本号或时间戳等信息,以确保在读取到数据和提交更新之间的时间段内,数据没有被其他事务修改。
    2. 如果数据在读取到和提交更新之间被其他事务修改了,则更新操作会失败,需要采取重试或其他策略。
    3. 乐观锁适用于读操作频繁的场景,因为它可以减少加锁的开销,提高系统的并发性能。
  • 示例

    • 在数据库中,可以使用版本号或时间戳字段来实现乐观锁。在读取数据时,获取版本号或时间戳;在更新数据时,检查版本号或时间戳是否发生了变化,如果没有变化则更新数据并更新版本号或时间戳,否则认为数据已经被其他事务修改过,更新操作失败。
    • 在Java中,虽然没有直接的乐观锁实现类,但可以通过版本号、时间戳等机制在代码中实现乐观锁的逻辑。例如,在更新数据时,先读取数据的版本号,然后更新数据并更新版本号,最后提交更新时检查版本号是否发生了变化。

五、死锁是什么?如何避免?

  1. 避免嵌套锁:当需要加锁多个对象时,应将它们的锁顺序统一,尽量避免嵌套锁。
  2. 使用tryLock()方法:可以使用ReentrantLock类的tryLock()方法,在获取锁时设置超时时间,避免一直等待而产生死锁。
  3. 避免无限期等待:在获取锁时,应设置一个等待的超时时间,即一段时间后如果还没有获取到锁,就放弃任务执行。
  4. 使用不同的锁:如果可以使用不同的锁来代替原有的锁,那么可以尝试使用不同的锁来避免死锁。
  5. 尽量减少锁的持有时间:如果持有锁的时间过长,就会增加死锁的可能性,因此需要尽量减少锁的持有时间。
  6. 避免使用多个锁:在程序中避免使用多个锁,因为使用多个锁会增加死锁的可能性。可以采用一些技巧来避免使用多个锁,如采用粗粒度锁,将多个细粒度锁合并成一个大锁。
  7. 按照规定的顺序申请锁:为了避免死锁,可以规定一个申请锁的顺序,在申请锁的时候按照规定的顺序进行申请。
  8. 统一管理锁资源:将锁资源的管理进行统一管理,可以更好地避免死锁。
  9. 使用死锁检测工具:可以使用一些工具(如Java探针、Eclipse自带的死锁检测等)来检测和解决死锁问题。

六、如何在Java中实现一个自定义的锁?

通常可以通过实现java.util.concurrent.locks.Lock接口来完成。Lock接口定义了一些基本的锁操作方法,如lock(), unlock(), tryLock(), tryLock(long timeout, TimeUnit unit)等

以下是一个简单的自定义锁的实现示例:

import java.util.concurrent.locks.Lock;  
  
public class CustomLock implements Lock {  
  
    private boolean isLocked = false;  
    private Thread lockedBy = null;  
  
    @Override  
    public void lock() {  
        Thread callingThread = Thread.currentThread();  
        while (isLocked && lockedBy != callingThread) {  
            // 等待锁被释放  
            try {  
                Thread.sleep(10); // 可以使用更复杂的等待策略  
            } catch (InterruptedException e) {  
                Thread.currentThread().interrupt(); // 恢复中断状态  
                throw new IllegalStateException("Interrupted while waiting for lock", e);  
            }  
        }  
        isLocked = true;  
        lockedBy = callingThread;  
    }  
  
    @Override  
    public void unlock() {  
        if (Thread.currentThread() != lockedBy) {  
            throw new IllegalMonitorStateException("Thread does not own lock");  
        }  
        isLocked = false;  
        lockedBy = null;  
    }  
  
    @Override  
    public boolean tryLock() {  
        if (!isLocked) {  
            isLocked = true;  
            lockedBy = Thread.currentThread();  
            return true;  
        }  
        return false;  
    }  
  
    @Override  
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {  
        long nanos = unit.toNanos(time);  
        long deadline = System.nanoTime() + nanos;  
        Thread callingThread = Thread.currentThread();  
        while (isLocked && lockedBy != callingThread) {  
            if (nanos <= 0) {  
                return false;  
            }  
            nanos = deadline - System.nanoTime();  
            // 使用nanos进行更精确的等待  
            Thread.sleep(nanos / 1000000, (int) (nanos % 1000000));  
        }  
        isLocked = true;  
        lockedBy = callingThread;  
        return true;  
    }  
  
    @Override  
    public Condition newCondition() {  
        throw new UnsupportedOperationException("Conditions not supported");  
    }  
}

七、什么是可重入锁(ReentrantLock)?

可重入锁(ReentrantLock),也称为递归锁,是一种支持同一个线程多次获取同一个锁的锁机制。在并发编程中,当一个线程获得了锁之后,如果再次尝试获取同一个锁时,可重入锁会允许该线程继续获取锁而不会被阻塞。这种机制允许线程在执行过程中多次获取同一个锁,并且在释放锁之前需要相同次数的解锁操作。

可重入锁的主要目的是解决在递归调用或嵌套代码中的锁定问题。当一个线程已经获得了锁,但在持有锁的代码块中又调用了另一个需要同样锁的方法时,如果使用非可重入锁,线程会因为无法再次获得同一个锁而陷入死锁状态。而可重入锁允许线程多次获得同一个锁,避免了死锁问题。

在Java中,ReentrantLock类是可重入锁的一种实现方式。这个类实现了Lock接口,提供了比内置锁(synchronized关键字)更多的灵活性和功能。ReentrantLock支持公平性设置,使得等待时间最长的线程优先获取锁。此外,ReentrantLock还提供了可中断的获取锁(lockInterruptibly()方法)和尝试获取锁(tryLock()方法)的功能,进一步增加了其灵活性。

使用可重入锁的场景包括递归函数、锁的嵌套、锁的互斥和锁的继承等,即任何需要在同一线程中多次获取同一把锁的场景,以及需要在方法调用链中多次获取同一把锁的场景。

八、什么是公平锁和非公平锁?

公平锁和非公平锁是两种类型的锁机制,它们的主要区别在于线程获取锁的顺序。

  • 公平锁:公平锁是指多个线程按照申请锁的顺序来获取锁。也就是说,如果一个线程比另一个线程早地请求了某个锁,那么在释放锁时,等待时间最长的线程(也就是最早请求锁的线程)会获得该锁。这种策略保证了线程间的公平性,但可能会导致整体的效率降低,因为线程需要等待更长的时间来获取锁。
  • 非公平锁:非公平锁则不保证线程获取锁的顺序。在释放锁时,任何等待的线程都有可能立即获得该锁,而不管它们等待时间的长短。这种策略可能导致某些线程长时间得不到锁,从而产生“饥饿”现象。但是,非公平锁的整体效率通常比公平锁高,因为线程在等待锁时不需要进行额外的排序或调度操作。

在Java中,ReentrantLock类提供了公平锁和非公平锁的实现。通过构造函数的参数,我们可以指定锁是公平的(true)还是非公平的(false)。默认情况下,ReentrantLock是非公平锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/603646.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何缩小图片尺寸不改变清晰度?几个方法教你解决

在平时对图片进行处理的时候&#xff0c;最害怕的就是修改过的图片质量下降&#xff0c;导致清晰度不够&#xff0c;尤其是缩小图片尺寸的时候&#xff0c;所以今天小编就来告诉大家几个关于修改图片尺寸又不改变清晰度的方法。 修改图片大小是非常普遍的图片编辑需求&#xf…

【SpringMVC 】什么是SpringMVC(三)?基于springmvc的文件上传、基于springmvc的拦截器、基于springmvc的邮件发送

文章目录 SpringMVC第五章1、SpringMVC文件上传1、基本步骤1-2345-82、邮件发送1、基本步骤1-234-5567-8 简单邮件带附件的邮件第六章1、拦截器的使用使用步骤232、调度的使用基本步骤1-56-8调度规则3、shiro安全框架核心概念基本语法1、基于ini文件的认证**测视类**2、基于rea…

计算机组成原理网课笔记

无符号整数的表示与运算 带符号整数的表示与运算 原反补码的特性对比 移码

基于 docker-compose 部署 LNMP 架构

目录 前言 1、任务要求 2、Nginx 2.1 建立工作目录并上传相关安装包 2.2 编写 Nginx Dockerfile 脚本 2.3 准备 nginx.conf 配置文件 3、Mysql 3.1 建立工作目录并上传相关安装包 3.2 编写 Mysql Dockerfile 脚本 3.3 编写 my.cnf 配置文件 4、PHP 4.1 建立工作目录…

Spring MVC(一)

1 Spring MVC概述 我们在之前学习Servlet的时候&#xff0c;认识了在WEB开发中MVC设计模式&#xff0c;其最为经典的设计就是&#xff0c;通过控制器&#xff08;Controller&#xff09;分离模型&#xff08;Model&#xff09;和视图&#xff08;View&#xff09;。在具体的WEB…

提高谷歌抓取成功率:代理IP的7个使用误区

在当今数字化时代&#xff0c;数据采集和网络爬取已成为许多企业和个人必不可少的业务活动。对于爬取搜索引擎数据&#xff0c;特别是Google&#xff0c;使用代理IP是常见的手段。然而&#xff0c;使用代理抓取Google并不是一件轻松的事情&#xff0c;有许多常见的误区可能会导…

在IDEA中通过模块创建新项目的时候,出现无法连接的错误

1.找到IDEA中的设置 2.在设置搜索HTTP,选择自动检测代理设置 选择URL: 输入https://start.spring.io 3.点击应用&#xff0c;即可完成

面试算法-链表-反转链表(golang、c++)

目录 1、题目 2、解题思路 2.1 遍历、迭代 2.2 递归 3、源代码 3.1 c 3.2 golang 4、复杂度分析 4.1 遍历、迭代法 4.2 迭代法 1、题目 链表是一种常用的数据结构&#xff0c;链表的特点是插入、删除节点的效率非常高&#xff0c;因为他不需要移动其他任何元素&…

nginx--防盗链

盗链 通过在自己网站里面引用别人的资源链接,盗用人家的劳动和资源 referer referer是记录打开一个页面之前记录是从哪个页面跳转过来的标记信息 正常的referer信息 none&#xff1a;请求报文首部没有referer首部&#xff0c;比如用户直接在浏览器输入域名访问web网站&…

使用 Cython 加密 Python 代码防止反编译

文章目录 前言使用 Cython 加密 Python 代码环境Python 源代码编写 Cython 编译配置文件 编译查看输出文件使用 问题error: Microsoft Visual C 14.0 or greater is requiredpyconfig.h(59): fatal error C1083: 无法打开包括文件: “io.h”: No such file or directorydynamic…

【已解决】‘pip‘ 不是内部或外部命令问题

&#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;AI Sun&#xff0c;视频号&#xff1a;AI-行者Sun &#x1f388; 本文专栏&#xff1a;本文收录于《AI实战中的各种bug…

大模型微调之 在亚马逊AWS上实战LlaMA案例(三)

大模型微调之 在亚马逊AWS上实战LlaMA案例&#xff08;三&#xff09; 使用 QLoRA 增强语言模型&#xff1a;Amazon SageMaker 上 LLaMA 2 的高效微调 语言模型在自然语言处理任务中发挥着关键作用&#xff0c;但训练和微调大型模型可能会占用大量内存且耗时。在本文中&…

Springboot整合飞书向群组/指定个人发送消息/飞书登录

Springboot整合飞书向群组发送消息 飞书开放平台创建企业自建应用 添加应用能力-机器人 创建完成后&#xff0c;进入应用详情页&#xff0c;可以在首页看到 App Id 和 App Secret 在飞书pc端创建一群机器人 此处可以拿到该机器人的webhook地址,通过https的方式,也可以调用发送…

为什么说RK3562可以碾压PX30?

在如今的科技市场中&#xff0c;处理器的性能直接决定了设备的运行速度和用户体验。今天&#xff0c;我们将对比瑞芯微旗下的两款处理器&#xff1a;PX30与RK3562。RK3562比PX30的性价比究竟高在哪里&#xff1f; PX30 瑞芯微PX30是一款高性能的四核应用处理器&#xff0c;专…

Android单行字符串末尾省略号加icon,图标可点击

如图 设置仅显示单行字符串&#xff0c;末尾用省略号&#xff0c;加跟一个icon&#xff0c;icon可点击 tvName.text "test"val drawable ResourcesCompat.getDrawable(resources, R.mipmap.icon_edit, null)tvName.setCompoundDrawablesWithIntrinsicBounds(null,…

故障——蓝桥杯十三届2022国赛大学B组真题

问题分析 这道题纯数学&#xff0c;考察贝叶斯公式 AC_Code #include <bits/stdc.h> using namespace std; typedef pair<int,double> PI; bool cmp(PI a,PI b){if(a.second!b.second)return a.second>b.second;return a.first<b.first; } int main() {i…

在Leaflet中点对象使用SVG和Canvas两种模式的对比

目录 前言 一、关于SVG和Canvas 1、SVG知识 2、Canvas知识 3、优缺点 二、SVG和Canvas在Leaflet的使用 1、相关类图 2、Leaflet的默认展示方式 三、SVG和Canvas实例及性能对比 1、SVG模式及性能对比 2、Canvas优化 总结 前言 众所周知&#xff0c;在Leaflet当中&#…

vue3配置element-plus时间选择器中文显示

修改main.js import ElementPlus from element-plus import element-plus/dist/index.css // 引入中文包 import zhCn from "element-plus/es/locale/lang/zh-cn"; const app createApp(App) app.use(ElementPlus,{ locale: zhCn, }) //挂载 app.mount(#app)

白盒测试:覆盖测试及测试用例设计

白盒测试&#xff1a;覆盖测试及测试用例设计 一、实验目的 1、掌握白盒测试的概念。 2、掌握逻辑覆盖法。 二、实验任务 某工资计算程序功能如下&#xff1a;若雇员月工作小时超过40小时&#xff0c;则超过部分按原小时工资的1.5倍的加班工资来计算。若雇员月工作小时超过…

数据库系统理论——关系数据库

文章目录 一、关系&#xff08;数据结构&#xff09;1、概述2、名词解释3、关系模式、关系数据库、关系数据库模式4、基本关系的性质 二、关系操作&#xff08;数据操作&#xff09;三、关系的完整性1、实体完整性2 、参照完整性3、用户自定义的完整性 四、关系代数五、习题 前…
最新文章