首页 > 编程知识 正文

java乐观锁有哪些,乐观锁实现

时间:2023-05-05 11:40:37 阅读:129213 作者:3112

CAS (乐观锁定) CAS均称为比较和swap,与翻译进行比较和交换

同步是一个悲观的锁定,当线程被锁定时,其他线程只是挂起cas操作,是一个乐观的锁定。 他认为自己会得到摇滚,所以他会继续尝试直到成功; CAS机制在看到Compare和Swap后,应该知道CAS至少包含两种行为,分别是比较和交换。 现在的CPU专门为这两个动作提供CAH命令,CPU保证这两个操作一定是原子。 也就是说,比较和交换这两个操作要么全部完成,要么没有全部完成

CAS机制使用三个操作数、内存地址、旧预期值和要修改的值。

例如,a 1操作,a默认=0,

1表示多个线程修改一个值a时,将a copy一份一份地放入自己的线程内存空间中。 此时,期望值为a,要修改的值为a 1的结果,结果为1 (要修改的值)。 因为是多个线程,所以每个线程内部都得到a=1。

2 )然后,运行比较并且交换,将线程中的期望值与主内存中的a进行比较,如果相同,则提交。 否则,提交将失败,因为a的值已被另一线程修改(此比较和提交操作是原子的)。 提交失败后,线程将重新获取a的值并重复此操作。 这种重复叫做自旋

板栗:

前提:线程a、b、主存储器内的变量count=0;

线程a :要修改计数值,请将副本放入自己的内存中一份,然后执行操作1。 此时,线程a的计数预期值为0,要修改的值为1

线程b :还修改了b:count的值,还执行了1个操作。 此时,线程b中的count的期望值为0,要修改的值为1。

线程b :现在提交到主内存。 提交时判断预期值和主内存的count相同,因此提交成功。这时主内存 count =1

线程a :提交也开始了,但在判断时期待值为0,但主内存为1,不相等。 因此,提交失败,放弃这次提交。

线程a :如果提交失败,请重新执行步骤1中的操作。

这种方式最终可以理解为无阻塞多线程争夺资源的模型。

问题ABA

还是上面的栗子?

线程a执行1,操作时,线程b已经将1的结果提交到主存储器,但此时他又执行- 1的操作提交到主存储器,该过程快于线程a。

此时线程a进行判断和交换,发现修正后的值与主存储器的值相同,并提出了计算结果。

线程a正在运行时,线程b更改了值并撤消了值。 结果没有变化,但数值被操作了

这就是典型的ABA问题

那么怎么解决?

其实解决起来非常简单,添加版本印章就可以了。 更新值时判断版本印章就可以了。

Java也有使用版本标记的实现。 是AtomicMarkableReference和AtomicStampedReference。

AtomicMarkReference :只关心这个变量是否被动

AtomicStampedReferrence :不仅仅是这个变量是否移动了,例如

val ASR=atomicstampedreferencestring (' 345 ), 0 ) fun main () valoldstamp=ASR.stampvaloldreference=ASR.reference println ($ old reference---- $ old stamp ' ) ) ' ${thread.currentthread ().name}当前变量值: ${asr.reference}当前版本: ${ASR.stamp}'${ asr.stamp, ASR.stamp1(} ) valT2=thread(println ) ($ ) thread.currentthread ) (.name} )当前变量值: ${asr.reference}当前条(' 34567 ',asr.stamp,asr.stamp 1) }(}T1.start ) (t1.join ) ) ) ) 652 )

tamp}")}345 ---- 0Thread-0 当前变量值:345 当前版本:0 trueThread-1 当前变量值:3456 当前版本:1 true34567 ---- 2

在修改字符串的时候,要传入已经修改过的字符串和版本号,负责就会修改错误

开销问题

在 CAS 期间,线程是不会休息的,线程如果长时间无法提交,内部就一直在进行自旋,这样就会产生比较大的内存开销

CAS 只能够保证一个共享变量的原子操作

CAS 只能保证对一个内存地址进行原子操作,所以说使用范围会有一定限制

例如:如果在执行 a+1 的下面加上,b+1,c +1,这种情况就会出现问题,这种时候反而使用 Syn 比较方便

其实 Java 中也提供了可以修改多个变量的原子操作

AtomicReference:将需要修改的包装成一个对象,然后使用 AtomicReference 的 compareAndSet 方法进行替换即可

fun main() { val user = User("幸福的过客", 31) val atomicReference = AtomicReference<User>(user) println("${atomicReference.get().name} --- ${atomicReference.get().age}") atomicReference.compareAndSet(user, User("忐忑的哑铃", 20)) println("${atomicReference.get().name} --- ${atomicReference.get().age}")}class User(val name: String, val age: Int)

上面的代码就保证了修改多个变量,实际上就是更新对象

实例

在 java 的 atomic 包下,一系列以 Atomic 开头的包装类,如 AtomicBoolean AtomicInteger 等,分别用于 int,bool,long 等类型的原子操作,其原理都是用的 cas

核心实现如下:

//使用将给定函数应用于当前值和给定值的结果,以原子方式更新当前值,并返回更新后的值。 该函数应无副作用,因为当尝试更新由于线程间争用而失败时,可能会重新应用该函数。 应用该函数时,将当前值作为其第一个参数,并将给定的update作为第二个参数 public final int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) { //预期值和要更新的值 int prev, next; do { //获取到预期值,也就是当前值 prev = get(); //计算要更新的值 next = accumulatorFunction.applyAsInt(prev, x); //更新成功则退出循环,否则重新计算 } while (!compareAndSet(prev, next)); return next;} //注意:这个比较并且 set 的操作是原子性的 //参数:期望–期望值 更新–新价值 //返回值: //如果成功,则为true 。 错误返回表示实际值不等于期望值public final boolean compareAndSet(int expect, int update) { return U.compareAndSwapInt(this, VALUE, expect, update);}

如果本文有帮助到你的地方,不胜荣幸,如有文章中有错误和疑问,欢迎大家提出!

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。