Java并发(二)-原子性和Atomic类
来源:互联网 发布:盼盼交友软件 编辑:程序博客网 时间:2024/06/08 15:32
上一个博文中说了多线程运行中的内存可见性问题,这次说一下原子性问题,可以这样原子是组成物质的基本粒子(在这里忽略质子夸克等微观粒子),可以看成一个操作是不可分割的。
典型的例子:
int i = 0;i = i++;System.out.println(i);//i=10
这个例子里面i的值为什么不为11呢?就是因为在jvm虚拟机执行这段代码的时候吧i++分成了三部分,读-改-写,导致代码编写的直观感觉和真是发生的不一样。这个例子的具体讲解参考下面的博客:
i=i++问题的讲解博客参考
举个例子:
创建一个类实现Runnable接口,并在run方法中读取自身的变量值
class AtomicDemo implements Runnable{ private volatile int value = 0; @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("value:"+getValue()); } protected int getValue(){ return value++; }}
在测试类的main函数中创建10个线程,每个线程都调用run方法来打印value的值
public class TestAtomic { public static void main(String[] args) { AtomicDemo ad = new AtomicDemo(); for(int i=0;i<10;i++){ new Thread(ad).start(); } }}
输出结果:
value:1value:5value:4value:3value:2value:0value:6value:1value:0value:7
我们发现0和1出现了两次,这是为什么呢?明明加上了volatile关键字了?
原因分析
根据之前了解的,在jvm调用的时候i=i++其实是分为了三部的,假若i=0
1. 先把i的值压倒栈顶此时是0
2. 再对i执行++操作,此时i的值是1,但是该步骤不影响栈顶的值
3. 然后把栈顶的值给i,此时栈顶的值是0,所以i就是0
4. 也就是说,i++是在赋值给j之前就完成的,但是j得到的值确实jvm虚拟机相当于中间变量一样的值。
看到这里,我们大概了解的原因了,(即便还不清楚,只要知道i++不是一部完成的就好了)正是因为i++被不是一步完成的,即便是被volatile修饰也会存在问题,加入A线程对i++,加完之后还没赋值,B线程得到了cpu的执行权,获得i为改变之前的值也进行++操作,B线程执行完了,A开始把值刷新并打印,此时发信两个的值是一样的。
解决方法CAS算法
什么是CAS算法:
CompareAndSwap(比较并且交换)(计算机硬件底层对 CAS 进行的支持 JVM 支持该算法)
在java.util.concurrent.atomic 包下提供了一些原子变量
1. volatile :保证了内存可见性
2. CAS算法保证了原子性
CompareAndSwap(比较然后交换)
CAS 算法是硬件对于并发操作的支持
CAS 包含了三个操作数:
内存值:V
预估值:A (栈顶的旧值)
更新值:B
当且仅当 V == A 的时候,V = B;否则不作任何操作;
====== 理解 CAS 算法:
当线程A,B两个对主内存的i(i=0)进行操作的时候,A,B同时获取了i的值V,放在栈顶,此时栈顶
的值为0,然后在自己的线程缓存中有一个拷贝B,对B进行加1操作,B的值为1,然后准备向内存中写
值的时候先判断V和栈顶的值是否相同,如果V的值没有改变则就把B的值赋给V,这个时候线程B也准备
向V中赋值,此时C判断自己栈顶的值和V的值,发现不一样,就不会给V进行赋值的,这样就保证了公共
变量的原子性,不会被重复赋值。
修改AtomicDemo类:
class AtomicDemo implements Runnable{ private AtomicInteger ai = new AtomicInteger(); @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("value:"+getValue()); } protected int getValue(){ return ai.getAndIncrement();//先得到在加加,相当于++操作 }}
主函数不用做修改,执行结果为:
value:0value:4value:5value:6value:3value:7value:1value:2value:8value:9
执行多次之后发现之前的的重复的情况不见了,可见CAS算法保证了原子性
模拟CAS算法
这里的模拟仅仅是对原理的理解
/* * 模拟 CAS 算法 * */class MyCas{ // 定义一个被 volatile 修饰的变量 value private volatile Integer value; //synchronized修饰return value方法 ,保证线程安全 public synchronized Integer getValue(){ return this.value; } // 比较 预估值(旧值)类似i++返回的是旧值 public synchronized Integer compareAndSwap(Integer expectedValue,Integer newValue){ // 先把得到旧值 Integer oldValue = this.value; // 判断旧值和预估值(栈顶的值)是否一样 if(oldValue == expectedValue){ //满足条件就把新值赋给value this.value = newValue; } return oldValue; } //设置新值返回boolean类型表示是否设置成功 public synchronized boolean compareAndSet(Integer expectedValue,Integer newValue){ return expectedValue == compareAndSwap(expectedValue, newValue); }}
测试方法:
final MyCas mc = new MyCas(); for(int i=0;i<10;i++){ new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } boolean b = mc.compareAndSet(mc.getValue(),(int)(Math.random()*100)); System.out.println(b); } }).start(); }
输出:
falsefalsefalsefalsefalsefalsetruefalsefalsefalse
创建10个线程随机修改cas对象的value值,观察输出结果,当值值合同的时候不会修改原来的值,保证了原子性
- Java并发(二)-原子性和Atomic类
- Java多线程/并发16、Atomic原子变量和原子操作
- java Atomic原子性
- java.util.concurrent.atomic 并发包下的原子操作类(AtomicBoolean,AtomicInteger,AtomicLong......))
- Java并发编程-35-原子变量-atomic
- Java并发总结(二):同步与原子性
- Java并发总结(二):同步与原子性
- Java并发中的原子操作包java.util.concurrent.atomic
- java Atomic原子更新
- java并发-原子性和可见性(7)
- 深入理解Atomic原子操作和volatile非原子性
- Java多线程(二)之Atomic:原子变量与原子类
- Java多线程(二)之Atomic:原子变量与原子类
- Java多线程(二)之Atomic:原子变量与原子类
- Java多线程(二)之Atomic:原子变量与原子类
- Java多线程(二)之Atomic:原子变量与原子类
- 原子性操作(atomic operation)
- 原子操作(Atomic)
- ERROR net.sf.ehcache.distribution.MulticastRMICacheManagerPeerProvider
- java通过JDBC链接SQLServer2012
- 【Linux】linux下atexit()函数的使用
- Android 数据库事务的个人理解
- [BZOJ 1177][Apio2009]Oil:DP
- Java并发(二)-原子性和Atomic类
- 53. Maximum Subarray
- team training 5 F 最小生成树
- PHP复习第二天PDO
- MapReduce编程实例:连接(Join)
- 学习淘淘商城第四十四课(首页跳转到搜索页面及搜索实现分析)
- 判断字符串是否回文
- ROC曲线,PR曲线,F1值和AUC概念解释及举例说明
- NIO Buffer代码示例