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



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

    }

     

  • 相关阅读:
    Servlet
    javaBean
    堆排序
    快速排序
    JSP9大内置对象
    include指令与<jsp:include>动作标识
    JSP指令标识
    TreeSet
    使用Docker存储之卷Volume
    Java学习之HttpClient的GET与POST请求
  • 原文地址:https://www.cnblogs.com/huangleshu/p/10026222.html
Copyright © 2020-2023  润新知