并发编程三要素:原子性,有序性,可见性 | 您所在的位置:网站首页 › 无代码编程是指什么 › 并发编程三要素:原子性,有序性,可见性 |
并发编程三要素
原子性: 一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。有序性: 程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)可见性: 一个县城对共享变量的修改,另一个线程能够立刻看到。
一、原子性
线程切换会带来原子性的问题 int i = 1; // 原子操作 i ; // 非原子操作,从主内存读取 i 到线程工作内存,进行 1,再把 i 写到主内存。虽然读取和写入都是原子操作,但合起来就不属于原子操作,我们又叫这种为“复合操作”。 我们可以用synchronized 或 Lock 来把这个复合操作“变成”原子操作。 例子: //使用synchronized private synchronized void increase(){ i ; } //使用Lock private int i = 0; Lock mLock = new ReentrantLock(); private void increase() { mLock.lock(); try { i ; } finally{ mLock.unlock(); } }这样我们就可以把这个一个方法看做一个整体,一个不可分割的整体。 除此之前,我们还可以用java.util.concurrent.atomic里的原子变量类,可以确保所有对计数器状态访问的操作都是原子的。 例子: AtomicInteger mAtomicInteger = new AtomicInteger(0); private void increase(){ mAtomicInteger.incrementAndGet(); } 二、可见性缓存导致可见性问题 即使是在执行完线程里的 i 后再执行线程 B,线程 B 的输入结果也会有 2 个种情况,一个是 0 和1。 因为 i 在线程 A(CPU-1)中做完了运算,并没有立刻更新到主内存当中,而线程B(CPU-2)就去主内存当中读取并打印,此时打印的就是 0。 volatile关键字主要有以下几个作用: 确保共享变量的可见性,即当某个线程修改了一个volatile变量的值时,其他线程能够立即看到该更改。禁止指令重排序,以确保程序的正确性。改变缓存的行为,使得每次访问volatile变量都需要直接从主内存中获取,而不是从本地缓存中获取。需要注意的是,volatile并不能解决所有的并发问题,例如它不能保证操作的原子性,也不能替代synchronized或Lock等同步机制。在编写多线程程序时,应根据具体情况选择合适的并发工具和策略。 synchronized和Lock能够保证可见性。 三、有序性导致有序性的原因是编译优化 我们都知道处理器为了拥有更好的运算效率,会自动优化、排序执行我们写的代码,但会确保执行结果不变。 例子: int a = 0; // 语句 1 int b = 0; // 语句 2 i ; // 语句 3 b ; // 语句 4这一段代码的执行顺序很有可能不是按上面的 1、2、3、4 来依次执行,因为 1 和 2 没有数据依赖,3 和 4 没有数据依赖, 2、1、4、3 这样来执行可以吗?完全没问题,处理器会自动帮我们排序。 在单线程看来并没有什么问题,但在多线程则很容易出现问题。 再来个例子: // 线程 1 init(); inited = true; // 线程 2 while(inited){ work(); }init(); 与 inited = true; 并没有数据的依赖,在单线程看来,如果把两句的代码调换好像也不会出现问题。 但此时处于一个多线程的环境,而处理器真的把这两句代码重新排序,那问题就出现了,若线程 1 先执行 inited = true; 此时,init() 并没有执行,线程 2 就已经开始调用 work() 方法,此时很可能造成一些奔溃或其他 BUG 的出现。 synchronized和Lock能确保原子性,能让多线程执行代码的时候依次按顺序执行,自然就具有有序性。 而volatile关键字也可以解决这个问题,volatile 关键字可以保证有序性,让处理器不会把这行代码进行优化排序。 |
CopyRight 2018-2019 实验室设备网 版权所有 |