JUC并发编程之:简单概述(四)
##本章内容:
无锁并发--乐观锁(非阻塞)
·CAS与volatile
·原子整数
·原子引用
·原子数组
·字段更新器
·原子累加器
·Unsafe
一、CAS与volatile
1、保护共享资源
·有一个账户,有1万元,现在有1000个线程,每个线程每次取10元,最后会剩下0元
##错误实现 与 加锁实现
@Slf4j
public class CasAndVolatile01 {
public static void main(String[] args) {
//unsafe实现
Account account1 = new AccountUnsafe(10000);
Account.demo(account1);
//加锁实现
Account account2 = new AccountLock(10000);
Account.demo(account2);
}
}
//加锁实现
class AccountLock implements Account{
Integer balance;
public AccountLock(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
synchronized (this){
return this.balance;
}
}
@Override
public void withDraw(Integer amount) {
synchronized (this){
this.balance -= amount;
}
}
}
//错误实现
class AccountUnsafe implements Account{
Integer balance;
public AccountUnsafe(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
return this.balance;
}
@Override
public void withDraw(Integer amount) {
this.balance -= amount;
}
}
interface Account{
//获取余额
Integer getBalance();
//取款
void withDraw(Integer amount);
//方法内启动1000个线程,每个线程-10元
//如果总额为10000,那么余额将会为0
static void demo(Account account){
List ts = new ArrayList<>();
for(int i=0;i<1000;i++){
ts.add(new Thread(()->{
account.withDraw(10);
}));
}
//计算起始时间
long start = System.nanoTime();
ts.forEach(Thread::start);
ts.forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getBalance()
+" cost : "+(end-start)/1000_000+"ms");
}
}
##结果:
310 cost : 80ms ##错误实现
0 cost : 69ms ##加锁实现
##无锁实现
//无锁实现
class AccountCas implements Account{
AtomicInteger balance;
public AccountCas(Integer balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
return balance.get(); //底层是volatile
}
@Override
public void withDraw(Integer amount) {
while(true){
//余额最新值
Integer prev = balance.get();
//修改后的余额
Integer next = prev - amount;
//同步到主存--比较并设置
if(balance.compareAndSet(prev,next)){
break;
}
}
}
}
##结果:
160 cost : 85ms ##错误实现
0 cost : 73ms ##加锁实现
0 cost : 65ms ##无锁实现
2、CAS与volatile
CAS:
·上面AtomicInteger的解决方法,内部并没有使用锁来保护共享变量的线程安全,它的实现原理:
@Override
public void withDraw(Integer amount) {
while(true){
//余额最新值
Integer prev = balance.get();
//修改后的余额
Integer next = prev - amount;
//同步到主存---比较并设置
if(balance.compareAndSet(prev,next)){
break;
}
}
}
其中compareAndSet,它的简称就是CAS(也称 compare and swap),它必须是原子操作
·CAS底层是lock cmpxchg指令(X86架构),在单个CPU和多核CPU下都能够保证【比较-交换】的原子
性
·在多核状态下,某个执行到带lock的指令时,CPU会让总线锁住,当这个核把此指令执行完毕,再开启
总线,这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。
【CAS会在修改前判断 自己共享变量修改之前读到的数据和当前的数据是否一致,如果不一致则返回
·false,重新修改,如果一致则返回true修改成功】
volatile:
·获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。
·它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到
主存中获取他的值,线程操作volatile变量都是直接操作主存。即一个线程对volatile变量的修改,
对另一个线程可见。
##注意:
volatile仅保证了共享变量的可见性,让其他线程能够看到最新值,但不能解决指令交错问题
(不能保证原子性)
·【CAS必须借助volatile才能读取到共享变量的最新值来实现【比较并交换】的效果】
##AtomicInteger部分源码:
public class AtomicInteger{
private volatile int value;
}
3、CAS效率分析
##为什么无锁效率高?
·无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而synchronized会让线程在没有获得
锁的时候,发生上下文切换,进入阻塞
(打个比方:线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车减
速,熄火,等被唤醒又得重新打火、启动、加速恢复到高速运行,代价比较大)
·但无锁情况下因为线程要保持运行,需要额外CPU的支持,CPU在这里就好比高速跑道,没有额外的跑
道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,
还是会导致上下文切换。
4、CAS特点
·CAS结合volatile可以实现无锁并发,适用于线程数少,多核CPU的场景下:
>CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃点亏
再重试呗
>synchronized是基于悲观锁的思想:最悲观的估计,得防着其他线程来修改共享变量,我上了锁你们
都别想改,我改完了解开锁,你们才有机会
>CAS体现的是无锁并发,无阻塞并发:
>>因为没有使用synchronized,所以县城不会陷入阻塞,这是效率提升的因素之一
>>但如果竞争激烈,可以想到重试必然发生,反而会影响效率
二、原子整数
##JUC并发包提供了一些供CAS实现工具类:
·AtomicBoolean
·AtomicInteger
·AtomicLong
##ActomicInteger常用方法:
@Slf4j
public class ActomicIntegerTest {
public static void main(String[] args) {
AtomicInteger i = new AtomicInteger(0);
System.out.println(i.incrementAndGet());//++i
System.out.println(i.getAndIncrement());//i++
System.out.println(i.addAndGet(5));//先+5然后get
System.out.println(i.getAndAdd(5));//先get然后+5
System.out.println(i.updateAndGet(x->x*5));//先乘以5后get
System.out.println(i.getAndUpdate(x->x*5));//先get然后乘以5
System.out.println(i.get());
}
}
updateAndGet( )底层源码:
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
三、原子引用
##由于我们的共享变量并不一定都是基本数据类型,比如说BigDecimal,我们就可以使用原子引用
保证该共享变量的线程安全
·AtomicReference
·AtomicMarkableReference
·AtomicStampedReference 有版本号的
3.1、AtomicReference
public class CasAndVolatile02 {
public static void main(String[] args) {
DecimalAccount account = new BigDecimalAccountCas(new BigDecimal("10000"));
DecimalAccount.demo(account);
}
}
class BigDecimalAccountCas implements DecimalAccount{
private AtomicReference balance;
public BigDecimalAccountCas(BigDecimal balance) {
this.balance = new AtomicReference<>(balance);
}
@Override
public BigDecimal getBalance() {
return balance.get();
}
@Override
public void withDraw(BigDecimal amount) {
while (true){
BigDecimal prev = balance.get();
BigDecimal next = prev.subtract(amount);
if(balance.compareAndSet(prev,next)){
break;
}
}
}
}
interface DecimalAccount{
//获取余额
BigDecimal getBalance();
//取款
void withDraw(BigDecimal amount);
//方法内启动1000个线程,每个线程-10元
//如果总额为10000,那么余额将会为0
static void demo(DecimalAccount account2){
List ts = new ArrayList<>();
for(int i=0;i<1000;i++){
ts.add(new Thread(()->{
account2.withDraw(BigDecimal.TEN);
}));
}
//计算起始时间
long start = System.nanoTime();
ts.forEach(Thread::start);
ts.forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account2.getBalance()
+" cost : "+(end-start)/1000_000+"ms");
}
}
3.2、ABA问题:
##主线程将共享变量从A---修改成---->C
##但在主线程修改过程中t线程将共享变量从A---修改成---->B---又修改成--->A
##主线程还是会修改成功,但主线程是无法感知共享变量的变化的
##代码如下:
@Slf4j
public class AtomicReferenceABA01 {
static AtomicReference at = new AtomicReference("A");
public static void main(String[] args) {
log.debug("main start...");
String prev = at.get();
other();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("main change A-->C : {}",at.compareAndSet(prev,"C"));
}
private static void other(){
new Thread(()->{
log.debug("t1 change A-->B : {}",at.compareAndSet("A","B"));
},"t1").start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
log.debug("t1 change B-->A : {}",at.compareAndSet("B","A"));
},"t2").start();
}
}
##结果:
10:06:54.024 [main] DEBUG xxx - main start...
10:06:54.119 [t1] DEBUG xxx - t1 change A-->B : true
10:06:54.620 [t2] DEBUG xxx - t1 change B-->A : true
10:06:55.634 [main] DEBUG xxx - main change A-->C : true
##如何才能让main线程感知到 共享变量被修改呢?
##只要有其他线程动过了共享变量,那么自己的CAS就算失败,这时仅比较值是不够的,
需要再增加一个版本号
3.3、AtomicStampedReference
@Slf4j
public class AtomicStampedReferenceABA02 {
static AtomicStampedReference asr =new AtomicStampedReference<>("A",0);
public static void main(String[] args) {
log.debug("main start....");
String prev = asr.getReference();
int stamp = asr.getStamp();
log.debug("main prev:{} , stamp:{}",prev,stamp);
other();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("main change A-->C : {}",asr.compareAndSet(prev,"C",stamp,stamp+1));
}
private static void other(){
new Thread(()->{
String prev = asr.getReference();
int stamp = asr.getStamp();
log.debug("t1 prev:{} , stamp:{}",prev,stamp);
log.debug("t1 change A-->B : {}",asr.compareAndSet(prev,"B",stamp,stamp+1));
},"t1").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
String prev = asr.getReference();
int stamp = asr.getStamp();
log.debug("t2 prev:{} , stamp:{}",prev,stamp);
log.debug("t2 change B-->A : {}",asr.compareAndSet(prev,"A",stamp,stamp+1));
},"t2").start();
}
}
##结果:
10:25:43.158 [main] DEBUG xxx - main start....
10:25:43.172 [main] DEBUG xxx - main prev:A , stamp:0
10:25:43.217 [t1] DEBUG xxx - t1 prev:A , stamp:0
10:25:43.217 [t1] DEBUG xxx - t1 change A-->B : true
10:25:44.227 [t2] DEBUG xxx - t2 prev:B , stamp:1
10:25:44.227 [t2] DEBUG xxx - t2 change B-->A : true
10:25:45.242 [main] DEBUG xxx - main change A-->C : false
3.4、AtomicMarkableReference
##AtomicStampedReference 通过版本号 可以记录 是否被修改了,和修改了几次
##AtomicMarkableReference 通过boolean标记 是否被修改
@Slf4j
public class AtomicMarkableReferenceTest {
public static void main(String[] args) {
GarbageBag bag = new GarbageBag("一个满了的垃圾袋");
AtomicMarkableReference amr = new AtomicMarkableReference<>(bag,true);
log.debug("main start...");
GarbageBag prev = amr.getReference();
log.debug("main prev:{}",prev);
new Thread(()->{
log.debug("清洁阿姨 更换垃圾袋: {}",amr.compareAndSet(prev,new GarbageBag("清洁阿姨放了一个新的垃圾袋"),true,false));
},"清洁阿姨").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("main 更换垃圾袋: {}",amr.compareAndSet(prev,new GarbageBag("main放了一个新的垃圾袋"),true,false));
}
}
@Data
@ToString
@AllArgsConstructor
class GarbageBag{
private String desc;
}
##结果:
10:41:53.070 [main] DEBUG xxx - main start...
10:41:53.086 [main] DEBUG xxx - main prev:GarbageBag(desc=一个满了的垃圾袋)
10:41:53.150 [清洁阿姨] DEBUG xxx - 清洁阿姨 更换垃圾袋: true
10:41:54.149 [main] DEBUG xxx - main 更换垃圾袋: false
四、原子数组
·AtomicIntegerArray
·AtomicLongArray
·AtomicReferenceArray
##原子数组可以在多线程中保护数组里的元素
@Slf4j
public class AtomicIntegerArrayTest {
public static void main(String[] args) {
//创建了容量为10个int的AtomicIntegerArray
AtomicIntegerArray array = new AtomicIntegerArray(10);
log.debug("第2个值:{}",array.get(1));
int[] array2 = new int[]{1,2,3,4,5,6,7,8,9,10};
AtomicIntegerArray array3 = new AtomicIntegerArray(array2);
log.debug("第2个值:{}",array3.get(1));
log.debug("第2个值+1:{}",array3.getAndIncrement(1));
log.debug("第2个值:{}",array3.get(1));
log.debug("第3个值+5:{}",array3.getAndAdd(2,5));
log.debug("第3个值:{}",array3.get(2));
log.debug("第4个值*7:{}",array3.getAndUpdate(3,t->t*7));
log.debug("第3个值:{}",array3.get(3));
log.debug("修改第5个值为999");
array3.set(4,999);
log.debug("第5个值:{}",array3.get(4));
}
}
##结果:
11:45:37.301 [main] DEBUG xxx - 第2个值:0
11:45:37.316 [main] DEBUG xxx - 第2个值:2
11:45:37.317 [main] DEBUG xxx - 第2个值+1:2
11:45:37.317 [main] DEBUG xxx - 第2个值:3
11:45:37.317 [main] DEBUG xxx - 第3个值+5:3
11:45:37.317 [main] DEBUG xxx - 第3个值:8
11:45:37.361 [main] DEBUG xxx - 第4个值*7:4
11:45:37.361 [main] DEBUG xxx - 第3个值:28
11:47:45.114 [main] DEBUG xxx - 修改第5个值为999
11:47:45.114 [main] DEBUG xxx - 第5个值:999
五、字段更新器
·AtomicReferenceFieldUpdater
·AtomicIntegerFieldUpdater
·AtomicLongFieldUpdater
##字段更新器可以在多线程中保护对象的某个属性\成员变量
@Slf4j
public class AtomicReferenceFieldUpdaterTest {
public static void main(String[] args) {
Student st = new Student("李四");
//参数:类,要更新字段的类型,要更新字段的名称
AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
log.debug("更新名称为张三:{}",updater.compareAndSet(st,"李四","张三"));
log.debug("st : {}",st);
}
}
@Data
@ToString
@AllArgsConstructor
class Student{
volatile String name;
}
六、原子累加器
##ActomicLong和LongAdder累加的性能比较
@Slf4j
public class LongAdderTest {
public static void main(String[] args) {
for (int i = 0; i <5 ; i++) {
demo(
()->new AtomicLong(0),
(adder)->adder.getAndIncrement()
);
}
log.debug("-------------");
for (int i = 0; i <5 ; i++) {
demo(
()->new LongAdder(),
(adder)->adder.increment()
);
}
}
private static void demo(Supplier supplier, Consumer consumer){
T adder = supplier.get();
List ts = new ArrayList<>();
//4个线程,每人累加50万
for (int i=0;i<4;i++){
ts.add( new Thread(()->{
for(int j=0;j<500000;j++){
consumer.accept(adder);
}
}));
}
long start = System.nanoTime();
ts.forEach(t->t.start());
ts.forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
log.debug("adder {}, cost : {} ms",adder,(end-start)/1000_000);
}
}
##结果:
14:57:20.170 [main] DEBUG xxx - adder 2000000, cost : 49 ms
14:57:20.228 [main] DEBUG xxx - adder 2000000, cost : 43 ms
14:57:20.265 [main] DEBUG xxx - adder 2000000, cost : 36 ms
14:57:20.303 [main] DEBUG xxx - adder 2000000, cost : 37 ms
14:57:20.344 [main] DEBUG xxx - adder 2000000, cost : 41 ms
14:57:20.344 [main] DEBUG xxx - -------------
14:57:20.359 [main] DEBUG xxx - adder 2000000, cost : 13 ms
14:57:20.364 [main] DEBUG xxx - adder 2000000, cost : 5 ms
14:57:20.370 [main] DEBUG xxx - adder 2000000, cost : 5 ms
14:57:20.377 [main] DEBUG xxx - adder 2000000, cost : 6 ms
14:57:20.382 [main] DEBUG xxx - adder 2000000, cost : 4 ms
##LongAdder性能提升的原因:
LongAdder性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Thread-0累加Cell[0]
而Thread-1累加Cell[1]...最后将结果汇总,这样他们在累加时操作的不同的Cell变量,因此减
少了CAS重试失败,从而提高性能
七、Unsafe
·Unsafe对象提供了非常底层的,操作内存、线程的方法
·Unsafe对象不能直接调用,只能通过反射获得
获取Unsafe:
@Slf4j
public class UnsafeAccessor {
public static void main(String[] args) {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
log.debug("unsafe:{}",unsafe);
}
}
Unsafe CAS操作:
/**
* 使用Unsafe线程安全的操作Teacher对象的成员变量
* (之前使用AtomicReferenceFieldUpdater)
*/
@Slf4j
public class UnsafeAccessor {
public static void main(String[] args) {
try {
//获取Unsafe
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
log.debug("unsafe:{}",unsafe);
Teacher tc = new Teacher(1,"张三");
//获取域的偏移地址
long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
//执行CAS操作
unsafe.compareAndSwapInt(tc,idOffset,1,2);
unsafe.compareAndSwapObject(tc,nameOffset,"张三","李四");
//检验
log.debug("tc : {}",tc);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
@Data
@ToString
@AllArgsConstructor
class Teacher{
volatile int id;
volatile String name;
}
Unsafe模拟原子整数:
@Slf4j
public class UnsafeForAtomicInteger {
public static void main(String[] args) {
MyAtomicInteger mat = new MyAtomicInteger(10000);
List ts = new ArrayList<>();
//1000个线程,每个线程减去10
for(int i=0;i<1000;i++){
ts.add(new Thread(()->{
mat.decrement(10);
}));
}
ts.forEach(t->t.start());
ts.forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
log.debug("mat : {}",mat.get());
}
}
class MyAtomicInteger{
private volatile Integer value;
private static final long valueOffset;
private static final Unsafe UNSAFE;
static{
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
valueOffset = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
throw new RuntimeException();
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
public MyAtomicInteger(Integer value) {
this.value = value;
}
public Integer get(){
return value;
}
public void decrement(Integer num){
while (true){
Integer prev = this.value;
Integer next = prev - num;
if(UNSAFE.compareAndSwapObject(this,valueOffset,prev,next)){
break;
}
}
}
}