多线程的共享变量的内存不可见性
转:https://www.cnblogs.com/huangleshu/p/10026222.html
/** * 线程的开销 : 线程的创建和销毁 * 线程的上下文切换和调度 * 线程的同步 * * * 多线程的内存模型: 线程独有的工作内存(线程缓存用于提高效率)---------所有线程共享的主内存 * * 线程读取在主内存的成员变量(即共享变量)的过程: * 1. 线程的工作内存会去读取主内存的成员变量并保存副本 * 2. 线程在工作内存中修改副本 * 3. 将修改后的副本的值推送给主空间并改写主空间该成员变量的值 * 4. 主空间成员变量修改后的值将不会主动推送给其他线程, 这就造成了线程的工作内存的共享变量的不同步 * * 问题: 各个线程的工作内存不可见 * 即 A线程先读取共享变量a, B线程修改了共享变量a后为a`,推送给主内存并改写, 主内存不会推送给A线程,A和B的变量会不同步 * * 解决办法 * synchroized可以同步值 * volatile关键字 会使得主内存的共享变量每经过一次改变都会推送给其他的线程, 其他线程会修改其副本 * * 同步值之synchronized和volatile的区别 * 相同点: * synchronized 和 volatile都能用来同步共享变量 * 不同点: * 1. volatile是轻量级的同步策略, 可以修饰基本类型的变量,如int, synchronized是重量级的同步策略,基于对象的同步锁 * 2. volatile不具备互斥性, 一个线程访问共享变量 , 其他线程也可以访问共享变量 * synchronized是互斥锁, 具备互斥性, 在被锁的代码块上只能有一个线程访问共享变量 * * 3. volatile不能保证变量的原子性, 即一组对共享变量的操作不具备事务(要么全部完成,要么全部不完成) 如 i++/i-- * 即一个线程在进行一组操作中还没完成时, 其他线程也能进入这组操作对共享变量进行修改 * 而 synchronized则能保证一组对共享变量操作的原子性, 即这组操作全部完成,才能进行下一轮操作 * 即在被锁的代码块中只能允许一个线程去执行这组操作, 其他需要执行这组操作的线程会进入阻塞状态,等待其完成 * * 总结: 主内存 工作内存 * 共享变量 副本 * 工作内存中会主动去拉去主内存的共享变量并创建其副本 * 工作内存中的副本修改后会推送给主内存改写共享变量 * volatile 会使得主内存修改后的共享变量推送其他线程 * * 内存不可见的本质 : 线程之间有互相独立的缓存即, 当多个线程对共享数据进行操作时, 其操作彼此不可见 * * 可以直接理解: 使用volatile之后该共享该变量线程不在工作内存缓存其副本, 所有线程对该变量的操作全是在主内存中完成 * 即不在存在操作的不可见,所有线程的操作的变量是位于主内存的变量 */ public class VolatileTest { public static void main(String[] args) throws InterruptedException { MyTask task = new MyTask(); new Thread(task).start(); while (true){ //读取值 //直接用线程缓存的值 不会去主内存去拉取变量 if (task.isFlag()){ System.out.println("============="); break; } /* synchronized (task){ if (task.isFlag()){ System.out.println("============="); break; } }*/ } } } class MyTask implements Runnable{ private volatile boolean flag = false; // 修改值 @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println("flag=" + flag); } public boolean isFlag() { return flag; } } /**多线程之数值运算 * i++的原子性问题: * int i=10; * int result = i++; * 结果result为10 ? * i++的实现步骤 : 读-改-返回值并写入 * 1. int temp = i * 2. i = i + 1; * 3. return temp 即 result = temp * i++和 ++i的区别是第3步: ++i return i 即 result = i * * i++的多线程操作的问题:会产生重复数据 *即在一个线程的工作内存对i的副本进行自增,但是没有推送给主内存更新i, 这是其他线程也读取了未更新i值 *本质 一组操作的原子性 * * volatile的不能保证i++操作同步的原因 * i++有读-改-写3步操作 ,需要保证这3个操作的原子性 , * volatile只能保证副本之间的可见性, 即volatile保证读-改-写的操作的共享变量是对主内存的变量i进行操作 * 不能保存多个操作的原子性,即在进行读-改-写这组3步操作时,其他线程也能进如这组操作 * 在写阶段即返回赋值前,其他线程会读取到未修改之前的i 这样多个线程会出现重复数据 * * 理解: 多线程: * 1. 考虑操作是否具有原子性,即底层的单个操作或多个操作 * 2. 操作具有原子性即单个操作(如赋值)则使用volatile * 3. 操作不具有原子性即多个操作,则使用互斥的同步锁 * * 使用场景 多线程中 原子性操作(不可分割的操作 比如赋值)可以直接用volatile进行数据同步 * 多个操作可以分割(非原子性操作)必须选用同步锁的互斥性保证这组操作的原子性(多个操作不可分割) * * 多线程i++的解决办法: java.util.concurrent.atomic包下的原子类: * 1. volatile修饰属性保证其原子的可见性 * 2. CAS(compare and swap)比较替换算法保证其原子性 * CAS算法是硬件对于并发操作共享数据的支持 * CAS包括是三个操作数: V内存值 A预估值(更新前会再次读取主存中的值) B更新值 * 步骤: if (V == A) V=b; 即通过比较来确认替换前的主存的值是否被修改, 只没有修改时才替换更新值 * (比同步锁效率要高, 即在多线程中,对数值的计算(包括++ --)操作优先使用原子类) */ public class AtomicTest { public static void main(String[] args) { AtomicDemo ad = new AtomicDemo(); for (int i = 0; i < 10; i++) { new Thread(ad).start(); } } } class AtomicDemo implements Runnable{ // private volatile int serialNumber = 0; private AtomicInteger serialNumber = new AtomicInteger(0); @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { } System.out.println(getSerialNumber()); } public int getSerialNumber(){ return serialNumber.getAndIncrement(); // return serialNumber++; } } /** * 模拟CAS算法 * CAS包括是三个操作数: V内存值 A预估值(更新前会再次读取主存中的值) B更新值 * if (V==A) V=B */ public class CompareAndSwapTest { public static void main(String[] args) { CompareAndSwap cas = new CompareAndSwap(); for (int i = 0; i < 10; i++) { new Thread(){ @Override public void run() { //1. 获取内存值 memoryValue 和 更新值newValue int memoryValue = cas.getValue(); System.out.println(cas.compareAndSet(memoryValue, new Random().nextInt(100))); } }.start(); } } } class CompareAndSwap{ private int value; //2. 获取预期值 expectedValue 并和memoryValue比较, 相等就设置值, 返回预期值 public synchronized int compareAndSwap(int memoryValue, int newValue){ //保存内存值OldValue(V) int expectedValue = value; if (expectedValue == memoryValue){ value = newValue; } //并回返回内存值OldValue(V) return expectedValue; } //3. 比较memoryValue 和 expectedValue,相等就返回成功标识true public synchronized boolean compareAndSet(int memoryValue, int newValue){ return memoryValue == compareAndSwap(memoryValue,newValue); } public synchronized int getValue() { return value; } }