0
点赞
收藏
分享

微信扫一扫

并发编程-Volatile解决JMM的可见性问题

上一篇 <<<Java内存模型(JMM)
下一篇 >>>Volatile的伪共享和重排序


Volatile的特性

  • a、保证变量对所有线程的可见性
  • b、禁止指令重排序优化------相当于一个内存屏障,不让顺序优化
    (指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)
    他不能够保证共享变量的原子性问题

CPU多核硬件架构剖析

产生可见性的原因

Volatile的代码展示

/**
 * 在启动的VM Options里加入:-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*JaryeThread.*
 * 即可查看volatile关键字使用的锁信息:
 0x000000010cf75c13: lock addl $0x0,(%rsp)     ;*putstatic flag
 ; - com.jarye.JaryeThread::<clinit>@1 (line 18)
 *
 *
 0x000000010cf751cf: lock addl $0x0,(%rsp)     ;*putstatic flag
 ; - com.jarye.JaryeThread::main@21 (line 35)
 **/
public class JaryeThread extends Thread {
    // 1.能够保证可见  当一个线程在修改我们主内存共享变量的数据 对另外一个线程可见
    /**
     * 汇编指令lock锁的机制 能够把让工作内存主动刷新主内存数据
     */
    private volatile   static boolean flag = true;
//
    @Override
    public void run() {
        while (flag) {
        }
    }

    public static void main(String[] args) {
        // 默认的情况下创建的线程为用户线程
        new JaryeThread().start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        // 主线程
        flag = false;
        System.out.println("主线程已经停止啦~~...");
    }
}

Volatile的底层原理

总线锁【维护解决cpu高速缓存副本数据之间一致性问题】

MESI缓存一致性协议

  • M修改 (Modified) 这行数据有效,数据被修改了,和主内存中的数据不一致,数据只存在于本Cache中。
  • E 独享、互斥 (Exclusive)这行数据有效,数据和主内存中的数据一致,数据只存在于本Cache中。
  • S 共享 (Shared)这行数据有效,数据和主内存中的数据一致,数据存在于很多Cache中。
  • I 无效 (Invalid) 这行数据无效。

Volatile为什么不能保证原子性


/**
 * volatile为什么不能保证原子性:
 * 线程1、线程2同时执行count++的操作,本地内存都做了变更,
 * 由于使用MESI协议,假设线程1的执行到主内存中时,总线嗅探器告诉线程2主内存已更改,直接将线程2的数据改为失效,从主内存中读取,导致了线程2的首次count++失效
 * 然后线程2再执行count++的操作,刷新到主内存中
 * 结论:线程2的首次count++失效,导致总体count的数据出现了偏差
 *
 **/
public class VolatileAtomThread {
    // 共享的全局变量
    private  volatile static int count = 0;

    /**
     * 一定会小于10000
     */
    public static void create() {
        count++;
    }

    public static void main(String[] args) {
        ArrayList<Thread> threads = new ArrayList<>();
        // 10次 10个线程 每个线程执行1000次
        for (int i = 0; i < 10; i++) {
            Thread tempThread = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    create();
                }
            });
            threads.add(tempThread);
            tempThread.start();
        }
        threads.forEach((t)-> {
            try {
                // 主线释放锁同时放弃cpu执行前 等待前面10个线程执行完毕
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 100001 10002
        System.out.println(count);
    }
}


相关文章链接:
<<<多线程基础
<<<线程安全与解决方案
<<<锁的深入化
<<<锁的优化
<<<Java内存模型(JMM)
<<<Volatile的伪共享和重排序
<<<CAS无锁模式及ABA问题
<<<Synchronized锁
<<<Lock锁
<<<AQS同步器
<<<Condition
<<<CountDownLatch同步计数器
<<<Semaphore信号量
<<<CyclicBarrier屏障
<<<线程池
<<<并发队列
<<<Callable与Future模式
<<<Fork/Join框架
<<<Threadlocal
<<<Disruptor框架
<<<如何优化多线程总结

举报

相关推荐

0 条评论