1、volatile
获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。
它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存,即一个线程对volatile变量的修改,对另一个线程可见。
注意:
volatile仅仅保证了共享变量的可见性,让其它线程能够开到新值,但不能解决指令交错问题(不能保证原子性)。
CAS必须借助volatile才能读取到共享变量的最新值来实现【比较和交换】的效果。
2、cas为什么无锁效率高?
- 无锁情况下,即使重试失败,线程始终在告诉运行,没有停歇,而synchronized会让线程在没有获取到锁的时候,发生上线问切换,进而阻塞。
- 但无锁情况下,因为线程要保持运行,需要额外的CPU支持,如果没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
3、CAS的特点
结合CAS和volatile可以实现无锁并发,适应于线程数少、多核CPU的场景下。
- CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,会重试。
- synchronized是基于悲观锁的思想:最悲观的估计,得防着其他线程来修改共享变量,上来先锁住,改完后再解开锁。
- CAS体现的是无锁并发、无阻塞并发。
- 因为没有使用synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一。
- 但如果竞争激励,可以想到重试必然频繁发生,反而效率会受到影响。
4、原子整数
J.U.C并发包提供了:AtomicBoolean、AtomicInteger、AtomicLong.
4.1、AtomicInteger
package com.example.test.java.juc;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;
public class AtomicTest {
public static void main(String[] args) {
AtomicInteger i = new AtomicInteger();
// 【原子操作】,先获取再自增(i=0,结果i=1,返回0),类似于i++
System.out.println(i.getAndIncrement());
// 【原子操作】,先自增再获取(i=0,结果i=1,返回1),类似于++i
System.out.println(i.incrementAndGet());
// 【原子操作】,先自减再获取(i=0,结果i=-1,返回-1),类似于--i
System.out.println(i.decrementAndGet());
// 【原子操作】,先获取再自减(i=0,结果i=-1,返回0),类似于i--
System.out.println(i.getAndDecrement());
// 【原子操作】,先获取再加值(i=0,结果i=5,返回0)
System.out.println(i.getAndAdd(5));
// 【原子操作】,先加值再获取(i=0,结果i=5,返回5)
System.out.println(i.addAndGet(5));
// 【原子操作】,先更新再获取(i=5,结果i=5*10 ,返回50), x 代表操作前的i值。
System.out.println(i.updateAndGet(x -> x *10));
// 【原子操作】,先获取再更新(i=5,结果i=5*10,返回5), x 代表操作前的i值。
System.out.println(i.getAndUpdate(x -> x *10));
// 原子操作实现原理:
updateAndGet(i, o -> o * 2,1);
System.out.println(i.get());
}
/* 原子操作实现原理(自定义实现)
*
* IntUnaryOperator : 接口函数。唯一的抽象方法:applyAsInt
*
* preOrNext : 返回处理前 或 处理后的 结果,1-前,2-后
*/
public static void updateAndGet(AtomicInteger i, IntUnaryOperator o,int preOrNext) {
int returni = 0;
while (true) {
int pre = i.get();
int next = o.applyAsInt(pre);
// compareAndSet为cas的原子操作
if (i.compareAndSet(pre,next)) {
if (1 == preOrNext) {
returni = pre;
} else {
returni = next;
}
break;
}
}
}
}
5、原子引用类型
AtomicReference、 AtomicMarkableReference、AtomicStampedReference
5.1、AtomicReference
package com.example.test.java.juc;
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;
/*
原子引用类型:
AtomicReference
AtomicMarkableReference
AtomicStampedReference
AtomicReference:
主线程仅能判断出共享变量的值与最初值A是否相同,不能感知到这种从A改为B又改回A的情况,
如果主线程希望:只要有其他线程【动过了】共享变量,那么自己的cas就算失败,这时仅比较值就不够用了,需要再加一个版本号。
如:AtomicStampedReference
*/
public class AtomicReferenceTest {
/*
DecimalAccount 接口类
*/
interface DecimalAccount {
// 获取金额
BigDecimal getBalance();
// 取款
void withdraw(BigDecimal amount);
}
/*
DecimalAccountCas 通过cas 实现DecimalAccount接口的提款方法的withdraw原子操作
*/
class DecimalAccountCas implements DecimalAccount {
// 通过原子引用类型保护Bigdecimal类型的余额:balance
private AtomicReference<BigDecimal> balance;
// 有参构造方法
public DecimalAccountCas(BigDecimal balance) {
this.balance = new AtomicReference<BigDecimal>(balance);
}
// 获取当前余额
@Override
public BigDecimal getBalance() {
return balance.get();
}
// 取款
@Override
public void withdraw(BigDecimal amount) {
while(true) {
// 当前余额
BigDecimal pre = balance.get();
// 取款后的余额
BigDecimal next = pre.subtract(amount);
// 通过cas原子操作,将取款后的值复制给余额
if (balance.compareAndSet(pre,next)) {
// 修改成功
break;
}
// 如果修改失败,代表pre已经被别人修改过了,需要继续循环重新获取
}
}
}
}
5.2、AtomicStampedReference (多了版本号)
AtomicStampedReference可以给原子引用加上版本号,追踪原子引用真个的变化过程,如:A->B->A->C, 通过AtomicStampedReference我们可以知道 引用变量中途被变更了几次。
package com.example.test.java.juc;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicStampedReference;
/*
AtomicStampedReference 比 AtomicReference 多了一个版本号的概念。
AtomicReference:
主线程仅能判断出共享变量的值与最初值A是否相同,不能感知到这种从A改为B又改回A的情况,
如果主线程希望:只要有其他线程【动过了】共享变量,那么自己的cas就算失败,这时仅比较值就不够用了,需要再加一个版本号。这时就可以使用 AtomicStampedReference。
AtomicStampedReference【只有当前值 、版本号都对,才可以修改成功】。
*/
@Slf4j(topic = "com.test.AtomicStampedReference")
public class AtomicStampedReferenceTestABA {
// 共享变量
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A",0);
public static void main(String[] args) throws InterruptedException {
// 获取未修改前的值
String pre = ref.getReference();
// 获取未修改前的版本号
int stamp = ref.getStamp();
log.debug("未修改前的值: {},版本号: {}",pre,stamp);
// 新开两个线程将A改为B, B改为A
new Thread(() -> {
int stamp1 = ref.getStamp();
String reference1 = ref.getReference();
log.debug("当前值:{},版本号: {}",reference1,stamp1);
boolean flag = ref.compareAndSet(ref.getReference(), "B", stamp1, stamp1 + 1);
log.debug("A改为B: {}",flag);
},"线程1").start();
new Thread(() -> {
int stamp2 = ref.getStamp();
String reference2 = ref.getReference();
log.debug("当前值:{},版本号: {}",reference2,stamp2);
boolean flag = ref.compareAndSet(reference2, "A", stamp2, stamp2 + 1);
log.debug("B改为A: {}",flag);
},"线程2").start();
Thread.sleep(1000);
// 尝试改为C
boolean flag = ref.compareAndSet(pre, "C", stamp, stamp + 1);
log.debug("A尝试改为C: {}",flag);
}
}
输出结果:
15:17:25.611 [main] DEBUG com.test.AtomicStampedReference - 未修改前的值: A,版本号: 0
15:17:25.702 [线程1] DEBUG com.test.AtomicStampedReference - 当前值:A,版本号: 0
15:17:25.702 [线程1] DEBUG com.test.AtomicStampedReference - A改为B: true
15:17:25.702 [线程2] DEBUG com.test.AtomicStampedReference - 当前值:B,版本号: 1
15:17:25.702 [线程2] DEBUG com.test.AtomicStampedReference - B改为A: true
15:17:26.704 [main] DEBUG com.test.AtomicStampedReference - A尝试改为C: false
但有时候,并不关系引用变量更改次数,只是单纯的关心【是否更改过】,所以就有了AtomicMarkableReference。
5.3、AtomicMarkableReference(是否更改过)
AtomicMarkableReference 是通过 boolean 型的标识来判断数据是否有更改过。
package com.example.test.java.juc;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicMarkableReference;
@Slf4j(topic = "com.test.AtomicMarkableReference")
public class AtomicMarkableReferenceTest {
static AtomicMarkableReference<String> ref = new AtomicMarkableReference("tom",false);
public static void main(String[] args) {
boolean oldMarked = ref.isMarked();
String oldReference = ref.getReference();
log.debug("初始化之后的标记:{}", oldMarked);
log.debug("初始化之后的值:{}", oldReference);
String newReference = "jerry";
boolean b = ref.compareAndSet(oldReference, newReference, true, false);
if (!b) {
log.debug("Mark不一致,无法修改Reference的值");
}
b = ref.compareAndSet(oldReference, newReference, false, true);
if (b) {
log.debug("Mark一致,修改reference的值为jerry");
}
log.debug("修改成功之后的Mark:{}" , ref.isMarked());
log.debug("修改成功之后的值:{}" , ref.getReference());
}
}
输出结果:文章来源:https://www.toymoban.com/news/detail-439505.html
15:42:45.757 [main] DEBUG com.test.AtomicMarkableReference - 初始化之后的标记:false
15:42:45.769 [main] DEBUG com.test.AtomicMarkableReference - 初始化之后的值:tom
15:42:45.769 [main] DEBUG com.test.AtomicMarkableReference - Mark不一致,无法修改Reference的值
15:42:45.770 [main] DEBUG com.test.AtomicMarkableReference - Mark一致,修改reference的值为jerry
15:42:45.770 [main] DEBUG com.test.AtomicMarkableReference - 修改成功之后的Mark:true
15:42:45.770 [main] DEBUG com.test.AtomicMarkableReference - 修改成功之后的值:jerry文章来源地址https://www.toymoban.com/news/detail-439505.html
到了这里,关于并发编程学习(十):共享模式无锁、原子整数、原子引用类型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!