Java面试——锁

公平锁: 是指多个线程按照申请锁的顺序来获取锁,有点先来后到的意思。在并发环境中,每个线程在获取锁时会先查看此锁维护的队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照 FIFO 的规则从队列中取到自己。

非公平锁: 指多个线程获取锁的顺序并不是按照申请锁的顺序,上来就尝试占有锁,如果尝试失败,就再采用类似公平锁的方式获取锁。有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

ReentrantLock:并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是false(非公平锁)。非公平的优点在于吞吐量比公平锁大。对于Synchronized锁也是一种非公平锁。

可重入锁(又名递归锁): 指同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码。也就是说,线程可以进入任何一个它已经拥有的锁,所同步的代码块。synchronizedunlock都是可重入锁。

//简单理解,就是方法1 是一个同步方法,里面包含了一个方法2 也是同步方法,但是当进入方法1后,也就获得了方法2的锁,即可重入锁
public synchronized void method1(){System.out.println("方法1 synchronized");method2();
}public synchronized  void method2(){System.out.printf("方法2 synchronized");
}

自旋锁: 是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式尝试获取锁,这样的好处是减少了上下文切换的消耗,确定是循环会消耗CPU。循环比较直到成功为止。

public final int getAndAddInt(Object var1, long var2, int var4){int var5;do{//根据对象和地址偏移量获取内存中的值var5 = this.getIntVolatile(var1, var2);//将获取到的值 var5 传入,此方法内部会先比较var2地址的值是否等于 var5,相等则修改var5值并返回,否则重新进入循环。}while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;
}

手写一个自旋锁: 思想就是通过while中的循环条件来充当锁,当条件成立时,表示未获得锁,进行死循环,直到while条件不成立,也就是获得锁。就退出死循环,执行业务逻辑。具体查看如下代码:

public class Test {public static void main(String[] args) throws Exception {/*执行结果展示:  AA   myLockBB   myLockAA    unLockBB    unLock*  分析:我们 AA 线程休眠了 5秒足以让 BB 线程执行结束,那为什么 BB 执行到 myLock 之后就没有继续执行呢。*  其实,BB 一直执行着,只不过陷入了 while 死循环中,因为 AA 将线程置为非空。*  等到 5 秒后,AA unlock 重新将线程=null时,BB 获取线程并执行任务。over*/Test test = new Test();new Thread(()->{test.myLock();try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}test.unLock();},"AA").start();TimeUnit.SECONDS.sleep(1);new Thread(()->{test.myLock();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}test.unLock();},"BB").start();}//对线程保证原子性AtomicReference<Thread> atomicReference = new AtomicReference<>();//获取锁,其实质,将锁看做一个条件判断,只要这个判断能够保证线程安全即可。//如下:我们将线程是否为空作为条件,如果是空的就没锁,自己可以对其加锁,将其值设为自己。//如果使用完,使用unlock 将线程设置为 null,其他线程通过判断来获得锁,其实就像一种约定而已。public void myLock(){Thread thread = Thread.currentThread();System.out.println(thread.getName()+"   myLock");while (!atomicReference.compareAndSet(null,thread)){}}//释放锁public void unLock(){Thread thread = Thread.currentThread();atomicReference.compareAndSet(thread,null);System.out.println(thread.getName()+"    unLock");}
}

自旋锁的优点主要包括:
【1】减少线程阻塞:对于锁竞争不激烈且锁占用时间短暂的情况,自旋锁能够显著提高性能,因为它减少了线程因阻塞而产生的上下文切换开销。
【2】避免内核态切换:与非自旋锁相比,自旋锁在尝试获取锁失败时会继续执行循环而不立即陷入内核态,这样可以避免线程在用户态和内核态之间的频繁切换,这在一定程度上提高了系统的整体性能。

然而,自旋锁也存在一些缺点:
【1】高负载下效率低下:如果锁竞争激烈或持有锁的线程需要长时间执行同步块,自旋锁会因为不断重复无效的旋转操作而导致性能下降。在这种情况下,自旋锁的消耗可能会超过线程阻塞后的恢复成本,因此应该关闭自旋锁以避免不必要的性能损失。1234
【2】可能存在不公平性:某些自旋锁实现(如Java中的)不是完全公平的,这意味着它们可能无法为等待时间最长线程提供优先权,这可能导致所谓的“线程饥饿”问题。
【3】单核处理器上的限制:在单核处理器上,自旋锁实际上没有真正的并行性,因为即使当前线程不阻塞其他线程,锁仍然不会被释放,导致资源的浪费。此外,如果处理器数量少于线程数量,自旋锁也可能造成不必要的资源浪费。4
【4】不适合计算密集型任务:如果任务主要是计算密集型的,使用自旋锁可能会导致性能下降,因为自旋锁会占用CPU资源,而在计算密集型任务中,减少锁的使用可能是更优的选择。

综上所述,自旋锁适用于锁竞争不太激烈且锁占用时间较短的场景,但在竞争激烈或锁占用时间较长的情况下,其性能优势不明显,甚至可能导致性能下降。

【独占锁】(写锁): 指该锁只能被一个线程所持有。对ReentrantLockSynchronized而言都是独占锁。
【共享锁】(读锁): 指该锁可被多个线程持有。

【1】不加读写锁时,代码及出现的问题如下:创建5个线程进行写入,5个线程进行读取。

public class ReadWriteLock {private volatile Map map = new HashMap();//写入方法public void put(String k,Object v){System.out.println(Thread.currentThread().getName()+"   开始写入:"+k );try {TimeUnit.MICROSECONDS.sleep(30);}catch (Exception e){e.printStackTrace();}map.put(k,v);System.out.println(Thread.currentThread().getName()+"   写入完成");}//读方法public void get(String k){System.out.println(Thread.currentThread().getName()+"   读数据开始:"+k );try {TimeUnit.MICROSECONDS.sleep(10);}catch (Exception e){e.printStackTrace();}Object v = map.get(k);System.out.println(Thread.currentThread().getName()+"   读数据完场完成"+v);}public static void main(String[] args) {ReadWriteLock readWriteLock = new ReadWriteLock();//写入数据for (int i=1;i<6;i++){final int tempInt = i;new Thread(()->{readWriteLock.put(tempInt+"",tempInt+"");},String.valueOf(i)).start();}//读取数据for(int i=1;i<6;i++){final int tempInt = i;new Thread(()->{readWriteLock.get(tempInt+"");},String.valueOf(i)).start();}}
}

【2】上述代码输出如下:第一个线程未写入完成时,其他线程就进入了该方法,进行了写操作。不符合多线程安全特性。

在这里插入图片描述

【3】加入读写锁:ReentrantReadWriteLock(读写锁)位于JUC包下

public class ReadWriteLock{private volatile Map map = new HashMap();private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();//写入方法public void put(String k,Object v){rwLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName()+"   开始写入:"+k );try {TimeUnit.MICROSECONDS.sleep(30);}catch (Exception e){e.printStackTrace();}map.put(k,v);System.out.println(Thread.currentThread().getName()+"   写入完成");}catch (Exception e){e.printStackTrace();}finally {rwLock.writeLock().unlock();}}//读方法public void get(String k){rwLock.readLock().lock();try {System.out.println(Thread.currentThread().getName()+"   读数据开始:"+k );try {TimeUnit.MICROSECONDS.sleep(10);}catch (Exception e){e.printStackTrace();}Object v = map.get(k);System.out.println(Thread.currentThread().getName()+"   读数据完场完成"+v);} catch (Exception e) {e.printStackTrace();} finally {rwLock.readLock().unlock();}}public static void main(String[] args) {ReadWriteLock readWriteLock = new ReadWriteLock();//写入数据for (int i=1;i<6;i++){final int tempInt = i;new Thread(()->{readWriteLock.put(tempInt+"",tempInt+"");},String.valueOf(i)).start();}//读取数据for(int i=1;i<6;i++){final int tempInt = i;new Thread(()->{readWriteLock.get(tempInt+"");},String.valueOf(i)).start();}}
}

【4】加入读写锁后,输出如下:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解指针2

各位小伙伴们&#xff0c;我们继续来学习指针&#xff0c;指针和结构体以及动态内存管理对后面的数据结构学习有非常大的帮助&#xff0c;所有我们一定要把这些知识点学会。OK,正式进入学习之旅吧 1.数组名的理解 在上⼀个章节我们在使⽤指针访问数组的内容时&#xff0c;有这…

【大数据】Flink SQL 语法篇(四):Group 聚合

Flink SQL 语法篇&#xff08;四&#xff09;&#xff1a;Group 聚合 1.基础概念2.窗口聚合和 Group 聚合3.SQL 语义4.Group 聚合支持 Grouping sets、Rollup、Cube 1.基础概念 Group 聚合定义&#xff08;支持 Batch / Streaming 任务&#xff09;&#xff1a;Flink 也支持 G…

SpringMVC 学习(四)之获取请求参数

目录 1 通过 HttpServletRequest 获取请求参数 2 通过控制器方法的形参获取请求参数 3 通过 POJO 获取请求参数&#xff08;重点&#xff09; 1 通过 HttpServletRequest 获取请求参数 public String handler1(HttpServletRequest request) <form action"${pageCont…

7.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-通过逆向分析确定游戏明文接收数据过程

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;通过逆向分析确定游戏明文发送数据过程 上一个内容中得出它是使用的send函数发送的数据包&#xff0c;所以接收数据它指定用的是recv函数接收的数据 然后在跳转recv函数分析时发现跳转到了wsock32.d…

【嵌入式移植】7、U-Boot源码分析4—链接脚本分析

U-Boot源码分析4—链接脚本分析 1 u-boot-spl.lds1.1 链接脚本的生成1.2 u-boot-spl.lds内容分析1.3 text - 程序代码段1.4 sram其它段定义1.4.1 .rodata只读数据段1.4.2 .data数据段1.4.3 .u_boot_list段 1.5 BSS段1.6 /DISCARD/ 从上一篇文章【嵌入式移植】6、U-Boot源码分析…

Linux字符设备驱动中同类型多设备节点的创建---一个驱动程序支持多个同类型设备

文章目录 前言1 代码解析1.1 驱动层1.2 应用层 2 运行结果总结 前言 本期分享的内容相对比较简单&#xff0c;那就是同时注册多个同类型的字符设备驱动&#xff0c;那么这样我们就可以同时支持多个同类型的设备了&#xff01;下面来带大家看一下&#xff1a; 1 代码解析 1.1 …

【Flink精讲】Flink性能调优:CPU核数与并行度

常见问题 举个例子 提交任务命令&#xff1a; bin/flink run \ -t yarn-per-job \ -d \ -p 5 \ 指定并行度 -Dyarn.application.queuetest \ 指定 yarn 队列 -Djobmanager.memory.process.size2048mb \ JM2~4G 足够 -Dtaskmanager.memory.process.size4096mb \ 单个 TM2~8G 足…

MySQL知识点总结(五)——锁

MySQL知识点总结&#xff08;五&#xff09;——锁 锁分类表锁 & 行锁如何添加表锁&#xff1f;如何添加行锁&#xff1f; 读锁 & 写锁行锁 & 间隙锁&#xff08;gap lock&#xff09;& 临键锁&#xff08;next-key lock&#xff09; 加锁机制分析可重复读隔离…

OpenAI视频生成Sora技术简析

基本介绍 Sora是春节期间OpenAI发布的产品&#xff0c;主要是通过文字描述生成视频&#xff0c;通过大规模视频数据训练而成的生成模型&#xff0c;当前还没开放试用。官方发布的技术报告&#xff1a;https://openai.com/research/video-generation-models-as-world-simulators…

趣学贝叶斯定理:贝叶斯定理的先验概率、似然和后验概率(1)

在第7章中&#xff0c;我们讨论了如何利用空间推理去推导贝叶斯定理。现在研究如何将贝叶斯定理当作一种概率工具&#xff0c;对不确定性进行逻辑推理。本章将利用贝叶斯定理来计算和量化在给定数据的情况下&#xff0c;信念有多大的可能性为真。为此&#xff0c;需要使用该定理…

019 Spring Boot+Vue 电影院会员管理系统(源代码+数据库+文档)

部分代码地址&#xff1a; https://github.com/XinChennn/xc019-cinema 一、系统介绍 cinema项目是一套电影院会员管理系统&#xff0c;使用前后端分离架构开发包含管理员、会员管理、会员卡管理、电影票、消费记录、数据统计等模块 二、所用技术 后端技术栈&#xff1a; …

【泰山派RK3566】智能语音助手(一)移植Kaldi语音转文字

文章目录 移植过程硬件资源下载测试 移植过程 参考我的这篇博客 【RV1126】移植kaldi实时语音识别 硬件 资源下载 链接&#xff1a;https://pan.baidu.com/s/1x1udT5eNzzQHoPOTCQ182A?pwdlief 提取码&#xff1a;lief –来自百度网盘超级会员V6的分享 下载的文件里面有一个…