Java 中的锁
乐观锁与悲观锁
乐观锁
乐观锁 是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号Version,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值
是否一样,一样则更新,否则失败
悲观锁
悲观锁 是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修
改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。
java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到才会转换为悲观锁,如RetreenLock。
CAS(Conmpare And Swap)
在计算机科学中, 比较和交换 (Conmpare And Swap)是用于实现多线程同步的原子指令,是一种无锁原子算法。
CAS的原理在这里插入图片描述
- 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。
- 操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成。
- 比较和交换 作为单个原子操作完成, 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。
仅当 V 值等于 A 值时,才会将 V 的值设为B,如果 V 值和A值不同,则说明已经有其他线程做了更
新,则当前线程则什么都不做。最后,CAS 返回当前 V 的真实值。CAS 操作时抱着乐观的态度进
行的,它总是认为自己可以成功完成操作。
CAS的局限性
- 循环时间长开销大:CAS长时间自旋不成功,给CPU带来很大的性能开销。
解决方法:JVM能支持pause指令,效率会有一定的提升。 - 只能保证一个共享变量的原子操作:对多个共享变量操作时,不能保证原子性。
解决方法:加锁;共享变量合并成一个共享变量
死锁
造成死锁的条件
- 互斥条件:一个资源(临界资源)每次只能为一个线程使用,而且使用期间是互斥的。
- 不可抢占的条件:一个线程已经占有资源,未经本线程释放的情况下,其它线程不能强行剥夺。
- 占有且申请条件:(部分分配条件)线程投入时,不是一次性申请所需资源,而是运行中按需要临时动态地申请。
- 循环等待条件:系统中几个线程形成循环地等待对方所占用资源的关系。
如何避免死锁
预防死锁有一个基本思想:要求线程申请资源时遵循某种协议,从而打破产生死锁的四个条件中的一个或几个,从而保证系统不会进入死锁状态。
- 减小了锁定的范围,避免发生交叉访问
- 涉及到要同时申请两个锁的方法中,总是以相同的顺序来申请锁
- 避免使用阻塞锁,我们可以使用Reentrant lock,也是可以避免死锁的