- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Java面试最容易被刷的重难点之锁的使用策略由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
在多线程的学习中,很多时候都要用到锁,但我们都知道,加锁这个操作是一个计算机中开销比较大的操作,因此,本篇文章我会带大家学习在不同场景中进行不同的加锁处理方式,以让程序更高效一些有关锁策略不仅仅局限于某一种语言,在很多语言中都可能会遇到加锁操作,而且这部分知识点也是面试中常见的问题,所以本篇文章内容基本都是需要大家自己认真理解并做到会用自己的语言组织起来的。内容均为博主认真总结,大家可以收藏起来慢慢学习,希望可以帮到大家哦! 。
。
。
乐观锁认为多个线程访问同一个共享数据时产生并发冲突的概率不大,并不会真的加锁, 而是直接尝试访问数据, 在访问的同时识别当前的数据是否出现访问冲突,若冲突,则会返回当前的错误信息让用户去决定如何去处理悲观锁会认为多个线程访问同一个共享数据时产生并发冲突的概率很大,因此往往会在取数据时会进行加锁操作,这样的话其他线程想要拿到这个数据时就会阻塞等到直到其他线程获取到锁 。
补充:在Java中synchronized这一加锁操作主要以悲观锁为主,初始使用乐观锁策略,但当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略 。
。
在生活中有很多情况都能涉及到乐观和悲观的心态,比如今天是阴天,A认为可能会下雨,会提前带好伞,这就对应到了悲观锁这一策略;而B比较乐观,不会认为会下雨,因此B不会带伞,这显然可以类比为乐观锁这一策略.
。
实现乐观锁策略这一功能的方式有很多,接下来我带大家去学习一种:基于版本号方式。 假设我们要使用多线程修改用户的账户余额,我们可以引入一个版本号来实现,具体方法如下:
设当前的余额为100,引入一个版本号version,将其初始值设为1,并且我们规定,提交版本必须大于数据库中记录的当前版本号才能执行更新余额的操作,若不满足此条件,则认为修改失败 。
图示 以线程1想把主内存中的数据减50,线程2把主内存中的数据减20为例:
线程1此时准备将主内存中的数据读入自己的工作内存中并修改,而线程2也想将主内存的数据读入自己的工作内存中并修改,此时线程1和线程2以及主内存中的版本号都为1 。
当线程1把主内存的数据减50后,即修改后,会将自己工作内存中的版本号加1,此时线程1工作内存中的版本号大于主内存中的版本号(2大于1),因此线程1成功修改了主内存中的数据,并将数据50写入主内存中,最后将主内存中的版本号加1(即为2) 。
此时线程2修改了自己工作内存中的数据,随后将自己的工作内存版本号改为2:
但正当线程2准备将其改好后的数据80写入主内存时,发现自己的版本号和主内存的版本号都一样,并不满足大于关系,因此此次修改失败,有效避免了多线程并发修改数据时引起的数据安全问题。 总结 。
基于版本号这样实现乐观锁的机制就是一种典型的实现方式,这个实现方式和之前所学过的单纯的互斥的加锁方式来说更加轻量一些(只修改版本号,只是在计算机中用户态上进行操作,而互斥加锁方式会涉及到用户态和内核态之间的切换,不仅效率不太高,也容易引起线程阻塞)对于这个机制来说,如果修改数据失败,就会涉及到重试操作,如果频繁重试的话那么效率也就不高了,因此,最好在线程并发冲突率比较低的场景下使用乐观锁这一方式比较好 。
。
。
。
。
我们都知道,当我们通过多线程方式尝试修改同一数据时,一般都可能引发线程安全问题,但当我们通过多线程方式尝试读取同一数据时,一般不会引发线程安全问题,因此,我们可以根据读和写的不同场景来给读和写操作分别加上不同的锁。 Java当中的synchronized不会对读和写进行区分,默认使用后线程都是互斥的 。
。
以Java为例,在标准库中存在这样一个类ReentrantReadWriteLock 源代码如下 。
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { private static final long serialVersionUID = -6992448646407690164L; /** Inner class providing readlock */ private final ReentrantReadWriteLock.ReadLock readerLock; /** Inner class providing writelock */ private final ReentrantReadWriteLock.WriteLock writerLock; /** Performs all synchronization mechanics */ final Sync sync; /** * Creates a new {@code ReentrantReadWriteLock} with * default (nonfair) ordering properties. */ public ReentrantReadWriteLock() { this(false); }
该类中提供了两个方法:
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
此方法可以创建出一个读锁实例 。
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
此方法可以创建出一个写锁实例 某个线程被读锁修饰后,这两个线程之间不会互斥,而是完全同时并发执行,一般将读锁用于线程读取数据比较多的场景;而当某个线程被写锁修饰后,这两个线程会互斥,一个线程会执行,而另一个线程会阻塞等待,因此必须是一个线程执行完了,另一个线程才会执行,一般用于修改数据比较多的场景 。
。
。
锁的核心特性 “原子性”,这样的机制追根溯源是 CPU 这样的硬件设备提供的 。
1.CPU 提供了 “原子操作指令”。 2. 操作系统基于 CPU 的原子指令,实现了 mutex 互斥锁. 3. JVM 基于操作系统提供的互斥锁。实现了 synchronized 和 ReentrantLock 等关键字和类.
注意:synchronized 并不仅仅是对 mutex 进行封装, 在 synchronized 内部还做了很多其他的工作 。
。
1.重量级锁依赖了OS提供的mutex,的开销一般很大,往往是通过内核来完成的 2.轻量级加锁一般不使用mutex,开销一般比较小,一般通过用户态就能直接完成 。
。
我们可以类比一个生活中的例子,当去银行办理业务时,如果是通过用户在银行工作人员的指导下自己在窗口外完成,那么效率会比较高,就像计算机中的用户态一样。而当我们把自己的业务交给银行相关人员去完成时,由于银行工作人员的闲忙时间是不可控的,因此无法保证效率,就好比计算机中的内核态.
。
当两个线程为了完成任务同时竞争一把锁时, 拿到锁的那个线程会立马执行任务,而没拿到就会阻塞等待,当一个线程把锁释放后,另一个线程不会被立即唤醒,而是等操作系统将其进行一系列的调度到CPU中的操作才能被唤醒然后执行任务,这种锁叫做挂起等待锁,线程在抢锁失败后进入阻塞状态,放弃 CPU,需要过很久才能再次被调度。但实际上,大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放,所以没必要就放弃 CPU。这个时候就可以使用自旋锁来处理这样的问题.
。
自旋锁的伪代码为:while (抢锁(lock) == 失败) {} 。
如果获取锁失败,就会立即再尝试获取锁,无限循环,直到获取到锁为止。第一次获取锁失败, 第二次的尝试会在非常短的时间内到来,一旦锁被其他线程释放, 就能第一时间获取到锁 。
。
自旋锁是一种典型的轻量级锁的实现方式,它没有放弃 CPU, 不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁,这样会大大提高代码的执行效率,但如果锁被其他线程持有的时间比较久, 那么就会持续地消耗 CPU 资源。(而挂起等待的时候是不消耗 CPU 的) 因此,我们应该注意自旋锁的适用场合:
注意:synchronized自身已经设置好了自旋锁和挂起等待锁,会根据不同的情况自动选择最优的使用方案 。
。
若有三个线程 A,B,C。 A先尝试获取锁,获取成功了,因为只有一把锁,所以B和C线程都会阻塞等待,那么如果A用完了锁后,B和C线程哪一个会最先获取到锁呢?
。
操作系统内部的线程调度就可以视为是随机的,如果不做任何额外的限制,锁就是非公平锁。如果要想实现公平锁,就需要依赖额外的数据结构(比如队列) 来记录线程们的先后顺序。公平锁和非公平锁没有好坏之分, 关键还是看适用场景(大部分情况下非公平锁就够用了,但当我们希望线程的调度时间成本是可控的,那么此时就需要用到公平锁了) 。
注意:synchronized为非公平锁 。
。
。
。
在介绍可重入锁和不可重入锁之前,大家先来思考一个问题,为什么Java中的main函数要用static来修饰?
public class Test { public static void main(String[] args) { }}
试想以下,如果main函数不是static来修饰的话:
public class Test { public void main(String[] args) { Test a=new Test(); a.main(); }}
那么此时这段代码能否被执行呢?答案是不能,因为在java中,没有static的变量或函数,如果想被调用的话,是要先新建一个对象才可以。而main函数作为程序的入口,需要在其它函数实例化之前就启动,这也就是为什么要加一个static。main函数好比一个门,要探索其它函数要先从门进入程序。static提供了这样一个特性,无需建立对象,就可以启动。也可以利用反证法说明,如果不是static修饰的,若不是静态的,main函数调用的时候需要new对象,new完对象才能调用main函数。那么你既想进入到main函数new对象,又想new完对象来调用main函数,那么就不行了,相当于自己把自己锁在里面出不来了 。
。
另外一个Java当中的例子:
synchronized void func1(){ func2(); } synchronized void func2(){ }
我们对func1这个方法进行加锁时,是可以成功的,但当我们对func2这个方法再次加锁后,就比较危险了。因为要执行完func1这个方法,就必须执行完func2,而此时锁已经被func1这个方法占用了,func2获取不到锁,所以func2就会一直阻塞等待,去等func1释放锁,但func1一直执行不完成,所以锁永远不会释放,func2永远也获取不到锁,这样就形成了一个闭环,相当于自己把自己锁在里面出不来了,此时这个线程就会崩溃,是比较危险的 。
。
了解了上面两个实例的严重性后,我们引入了可重入锁这个机制,当我们形成死锁后,如果是可重入锁的话,它不会让线程阻塞等待最终死锁从而奔溃,而是运用计数器的方法,去记录当前某个线程针对某把锁尝试加了几次,每加一次锁计数都会加1,每次解锁计数都会减1,这样当计数器里面的计数完全为0的时候才会真正释放锁,正是因为有了这样的机制,才有效避免了死锁问题。而在Java中,synchronized就是一把可重入锁,它给我们提供了很大的方便,保证在我们即使造成死锁问题时,程序也不至于崩溃.
。
。
如何理解乐观锁和悲观锁,具体实现方式如何 如何理解?
见乐观锁和悲观锁字面理解部分(尝试用自己的语言组织)实现方式: (1)乐观锁:见基于版本号方式实现乐观锁部分 (2)悲观锁:多个线程访问同一个共享数据时产生并发冲突时,会在取数据时会进行加锁操作,这样的话其他线程想要拿到这个数据时就会阻塞等到直到其他线程获取到锁 。
。
简单介绍一下读写锁 。
读写锁实际是一种特殊的自旋锁,它能把同一块共享数据的访问者分为读者和写者,读写锁会把读操作和写操作分别进行加锁,且读锁和读锁之间的线程不会发生互斥,写锁和写锁之间以及读锁和写锁之间的线程会发生互斥。读锁适用于线程读取数据比较多的场景,而写锁适用于线程修改数据比较多的场景.
。
简单介绍一下自旋锁 。
。
简单介绍一下Java中synchronized充当了哪些锁 。
到此这篇关于Java面试最容易被刷的重难点之锁的使用策略的文章就介绍到这了,更多相关Java 锁内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://blog.csdn.net/Mubei1314/article/details/120759983 。
最后此篇关于Java面试最容易被刷的重难点之锁的使用策略的文章就讲到这里了,如果你想了解更多关于Java面试最容易被刷的重难点之锁的使用策略的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
作者:小林coding 计算机八股文网站:https://xiaolincoding.com 大家好,我是小林。 今天跟大家聊聊,常见的缓存更新策略。 Cache Aside(旁路缓存)策略; Rea
我使用 git 多年,最近为了一个项目改用 mercurial。在过去的 6 个月里,我已经学会了如何通过命令行很好地使用 Mercurial。 这可能是我的想象,但在我看来,mercurial 在
这个问题适合任何熟悉的人 Node.js express Passport 带有 Passport 的 JWT 身份验证(JSON Web token ) Facebook OAuth2.0 或谷歌
在 Coq 中,当试图证明记录的相等性时,是否有一种策略可以将其分解为所有字段的相等性?例如, Record R := {x:nat;y:nat}. Variables a b c d : nat.
我正在处理的项目目前只有一个 Bootstrap 文件,用于初始化应用程序中的所有 javascript 对象。类似于下面的代码 if(document.getElementById('nav'))
我正在考虑使用 OpenLDAP 在首次登录时添加密码到期和强制更改密码。 似乎使用 ppolicy 覆盖来实现这一点。 当我在 ppolicy.schema 中看到这个时,我开始使用 ppolicy
这基本上是我昨天问的一个问题的重新陈述,因为我得到的一个答案似乎没有理解我的问题,所以我一定是不清楚。我的错。 因为 WPF 依赖于 DirectX,所以它对卡和驱动程序的内部非常敏感。我有一个案例,
我是单点登录(SSO)概念的新手。我开始知道 SAML 请求和响应是实现 SSO 流程的最佳方式。然后我开始阅读有关 SAML2.0 的信息。我来了一个术语 NameIdPolicy 在 saml1.
关闭。这个问题需要更多 focused .它目前不接受答案。 想改进这个问题?更新问题,使其仅关注一个问题 editing this post . 5年前关闭。 Improve this questi
关闭。这个问题是opinion-based 。目前不接受答案。 想要改进这个问题吗?更新问题,以便 editing this post 可以用事实和引文来回答它。 . 已关闭 9 年前。 Improv
在 Azure 上创建新的 SQL 数据库时,它将“计算+存储”选项设置为“2 vCore + 32GB 数据最大大小”作为默认配置,但我不想使用 vCore,我可以更改它。但问题是,是否可以通过策略
我希望创建一项策略,防止在未启用身份验证的情况下创建应用服务(仅审核它们是不够的)。 以下策略可以正确识别未启用身份验证的现有资源: { "mode": "All", "policyRule"
我正在尝试从现有 AuditIfNotExists 策略创建 DeployIfNotExists 策略。部署时不会出错,但会错误提示“没有相关资源与策略定义中的效果详细信息匹配”。当评估政策时。当我将
我正在尝试从现有 AuditIfNotExists 策略创建 DeployIfNotExists 策略。部署时不会出错,但会错误提示“没有相关资源与策略定义中的效果详细信息匹配”。当评估政策时。当我将
我正在使用 wunderground 的 json api 来查询我网站上的天气状况。 api 为我提供了一个包含所有必要数据的漂亮 json 对象,但我每天只能进行多次调用。存储这些数据的首选方式是
我有一个名为可视化数据结构的项目。我有这样的 OOP 设计。 Class VisualDataStructures extends JFrame Class ControlPanel extends
这个问题在这里已经有了答案: 关闭 14 年前。 副本: Use javascript to inject script references as needed? Javascript 没有任何指
Android 应用程序遇到了一些 ANR 问题,因此我实现了 StrictMode 策略。以前从未使用过这个,所以希望有人可以帮助解释以下内容: 为什么日志显示 2 个看似相似的违规行为,除了前 4
我目前正在尝试解决一个问题。假设我们在路上行驶,我们知道路上有 10 家酒店。每家酒店都有 0 到 6 星。我的问题是:找到选择星级酒店的最佳解决方案。唯一的问题是:您不能回头去参观您已经决定不去的酒
我正在将我的应用程序迁移到 MVP。从这个 konmik 中获得了有关静态演示者模式的提示 这是我的简要 MVP 策略。为简洁起见,删除了大部分样板和 MVP 监听器。这个策略帮助我改变了方向,证明了
我是一名优秀的程序员,十分优秀!