并发操作之——并发编程三要素
并发操作
并发操作之——并发编程三要素。
前言
并发操作之——并发编程三要素。
一、原子性
一个不可再被分割的颗粒,原子性指的是一个或多个操作要么全部执行成功要么全部执行失败,期间不能被中断,也不存在上下文切换,线程切换会带来原子性的问题 int num = 1; // 原子操作 num++; // 非原子操作,从主内存读取num到线程工作内存,进行 +1,再把num写到主内存, 除非用原子类,即java.util.concurrent.atomic里的原子变量类 解决办法是可以用synchronized 或 Lock(比如ReentrantLock) 来把这个多步操作“变成”原子操作,但是volatile,前面有说到不能修饰有依赖值的情况
public class XdTest { private int num = 0; //使用lock,每个对象都是有锁,只有获得这个锁才可以进行对应的操作 Lock lock = new ReentrantLock(); public void add1(){ lock.lock(); try { num++; }finally { lock.unlock(); } } //使用synchronized,和上述是一个操作,这个是保证方法被锁住而已,上述的是代码块被锁住 public synchronized void add2(){ num++; } }
解决核心思想:把一个方法或者代码块看做一个整体,保证是一个不可分割的整体
二、有序性:
程序执行的顺序按照代码的先后顺序执行,因为处理器可能会对指令进行重排序 JVM在编译java代码或者CPU执行JVM字节码时,对现有的指令进行重新排序,主要目的是优化运行效率(不改变程序结果的前提)
int a = 3 //1 int b = 4 //2 int c =5 //3 int h = a*b*c //4 上面的例子 执行顺序1,2,3,4 和 2,1,3,4 结果都是一样,指令重排序可以提高执行效率,但是多线程上可能会影响结果 假如下面的场景,正常是顺序处理 //线程1 before();//处理初始化工作,处理完成后才可以正式运行下面的run方法 flag = true; //标记资源处理好了,如果资源没处理好,此时程序就可能出现问题 //线程2 while(flag){ run(); //核心业务代码 }
指令重排序后,导致顺序换了,程序出现问题,且难排查 //线程1 flag = true; //标记资源处理好了,如果资源没处理好,此时程序就可能出现问题 //线程2 while(flag){ run(); //核心业务代码 } before();//处理初始化工作,处理完成后才可以正式运行下面的run方法
三、可见性:
一个线程A对共享变量的修改,另一个线程B能够立刻看到
// 线程 A 执行 int num = 0; // 线程 A 执行 num++; // 线程 B 执行 System.out.print("num的值:" + num);
线程A执行 i++ 后再执行线程 B,线程 B可能有2个结果,可能是0和1。 因为 i++ 在线程A中执行运算,并没有立刻更新到主内存当中,而线程B就去主内存当中读取并打印,此时打印的就是0;也可能线程A执行完成更新到主内存了,线程B的值是1。 所以需要保证线程的可见性 synchronized、lock和volatile能够保证线程可见性
总结
>并发操作之——并发编程三要素。