java 多线程 原子操作_Java多线程系列之:原子操作CAS

一,什么是原子操作?如何实现原子操作

1,synchronized可以完成原子操作,他是给予阻塞的锁的机制,但是有问题:

如果被阻塞的线程优先级很高怎么办?

拿到锁的线程一直不释放锁怎么办?

有大量线程进行竞争,消耗cpu。还容易出现死锁

锁的粒度比较大,影响性能。

二,CAS的原理(Compare And Swap:比较交换)

1,从指令级别保证这是一个原子操作。

每个CAS都包含三个运算符:

一个内存地址V

一个期望的值A

一个新值B

基本思路:

如果在内存地址V上进行操作,如果说这个地址上存放的值就是我期望的值A。就给地址V赋给新值B.

如果内存地址上不是我期待的A值,那就什么都不做。

在循环(这里用的是死循环,不带条件的一直在那里循环。又叫:自旋)里不断的进行CAS操作

利用了现代操作系统都支持CAS的指令,循环这个指令,直到成功为止。

其实是不再语言层面进行处理,而是把它交给了cpu和内存去实现,利用cpu的多处理能力,在硬件层面实现多线程安全。

三,CAS的问题

1,ABA问题

问题描述:

第一个线程拿到内存地址上的值。此时第二个线程也拿到内存地址上的值,然后修改成B,然后又改回去。

在第二个线程完成前面一顿操作之后,第一个线程才开始比较内存地址上的是否和A相等。那么比较的结果

肯定是相同的啦。第二个线程,修改过值,又修改回去。但是对于第二个线程的那些操作,第一个线程却不知道,这是有风险的。

怎么解决:引入版本号,每次变化,版本号都变化。这样每次数据变化都可以感知到

2,开销问题

在死循环里不断的进行操作,如果这个操作长期不成功,浪费cpu资源

3,只能保证一个共享变量的原子操作

一个内存地址只能指向一个变量,当有多个共享变量时,就无法保证操作的原子性。

那么如果有多个共享变量呢?我们可以把多个共享变量变成一个共享变量,比如把多个变量封装到一个类中。

四,JDK中相关原子操作类的使用

1,更新基本类型:

AtomicBoolean,AtomicInteger,AtomicLong

2,更新数组类:

AtomicInterArray,AtomicLongArray,AtomicReferenceArray

3,更新引用类型:

AtomicReference,AtomicMarkableReference,AtomicStampedReference

4,原子更新字段类:

AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater

五,实际使用

1,AtomicIntegerArray

public classUseAtomicArray {static int[] value = new int[]{1,2};static AtomicIntegerArray ai = newAtomicIntegerArray(value);public static voidmain(String[] args) {

ai.getAndSet(0,3);//上面的操作只能修改引用对象的值

System.out.println(ai.get(0));//实际的对象没有被改变

System.out.println(value[0]);

}

}

2,AtomicReference

/**

* 引用类型的原子操作类*/

public classUseAtomicReference {static AtomicReference userRef = new AtomicReference<>();public static voidmain(String[] args) {

UserInfo userInfo= new UserInfo("Mark",12);

userRef.set(userInfo);

UserInfo udpateUser= new UserInfo("Bill",14);

userRef.compareAndSet(userInfo,udpateUser);//引用的对象被修改了

System.out.println(userRef.get().getName()+":"+userRef.get().getAge());//原对象的值并没有改变

System.out.println(userInfo.getName()+":"+userInfo.getAge());

}static classUserInfo{privateString name;private intage;public UserInfo(String name,intage){this.name =name;this.age =age;

}publicString getName() {returnname;

}public voidsetName(String name) {this.name =name;

}public intgetAge() {returnage;

}public void setAge(intage) {this.age =age;

}

}

}

3,AtomicStampedReference

/**

* 带版本戳的原子操作类*/

public classUseAtomicstampedReference {static AtomicStampedReference asr = new AtomicStampedReference<>("mark",0);public static voidmain(String[] args) throws InterruptedException {

finalint oldStamp =asr.getStamp();

final String oldReference=asr.getReference();//获取初始值和初始版本号

System.out.println(oldReference+"--"+oldStamp);

Thread rightStampThread= new Thread(newRunnable() {

@Overridepublic voidrun() {

System.out.println(Thread.currentThread().getName()+"当前变量值:"+oldReference+

"当前版本戳:"+oldStamp+"-"+asr.compareAndSet(oldReference,

oldReference+"+Java",oldStamp,oldStamp+1));

}

});

Thread errorStampThread= new Thread(newRunnable() {

@Overridepublic voidrun() {

String reference= asr.getReference();//重新获取reference

int stamp =asr.getStamp();

System.out.println(Thread.currentThread().getName()+"当前变量值:"+reference+

"当前版本戳:"+stamp+"-"+asr.compareAndSet(reference,

reference+"+C",oldStamp,oldStamp+1));

}

});

rightStampThread.start();//确保让rightStampThread先执行

rightStampThread.join();//第二次修改会失败,因为上面的线程已经修改过一次了,再次在旧的版本号上修改是无法成功的

errorStampThread.start();

errorStampThread.join();

System.out.println("finally--->"+asr.getReference()+"-"+asr.getStamp());

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值