多线程的共享变量的内存不可见性


/**
* 线程的开销 : 线程的创建和销毁
* 线程的上下文切换和调度
* 线程的同步
*
*
* 多线程的内存模型: 线程独有的工作内存(线程缓存用于提高效率)---------所有线程共享的主内存
*
* 线程读取在主内存的成员变量(即共享变量)的过程:
* 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;
}

}

 

原文地址:https://www.cnblogs.com/huangleshu/p/10026222.html