zoukankan      html  css  js  c++  java
  • java学习:JMM(java memory model)、volatile、synchronized、AtomicXXX理解

    一、JMM(java memory model)内存模型

    从网上淘来二张图:

    java内存模型1

    上面这张图说的是,在多核CPU的系统中,每个核CPU自带高速缓存,然后计算机主板上也有一块内存-称为主内(即:内存条)。工作时,CPU的高速缓存中的数据通过一系列手段来保证与主内的数据一致(CacheCoherence),更直白点,高速缓存要从主内中load数据,处理完以后,还要save回主存。

    java内存模型1

    上图说的是,java中的各种变量(variable)保存在主存中,然后每个线程自己也有自己的工作内存区(working memory),工作时,线程从主存中把变量副本load到自己的工作内存区,处理完了,再save回主存。

    好象很明白,没有什么不好理解的:),

    问题来了,如果有二个线程:线程A与线程B, A从主存中读取了变量x(到自己的的工作内存区),正准备处理,这时B修改了主存中的变量x,线程A能看见这种变化吗?(是否需要及时从主存中,加载最新的值),这个问题称为共享变量的可见性。

    二、volatile、synchronized、AtomicXXX

    直接上码:

    2.1 版本1

    package test.cn.mwee.order.monitor;
    
    /**
     * Created by 菩提树下的杨过 on 2017/6/11.
     */
    public class ThreadTest extends Thread {
    
        private static boolean flag = false;
    
        public void run() {
            System.out.println("t1:" + Thread.currentThread().getId());
            while (!flag) {
    
            }
            System.out.println("quit!");
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadTest t1 = new ThreadTest();
            t1.start();
            Thread.sleep(50);
            ThreadTest.flag = true;
            System.out.println("main:" + Thread.currentThread().getId());
        }
    }
    

    ThreadTest是一个线程类,里面有一个静态变量flag,然后写了个main方法做测试。

    注:在t1启动完成后,主线程中修改了ThreadTest的静态变量值flag,这时t1的run方法里的while循环,其实是看不见主线程对这个值的修改,所以程序始终不能退出,打印不出那一行quit.

    2.2 版本2

    package test.cn.mwee.order.monitor;
    
    /**
     * Created by 菩提树下的杨过 on 2017/6/11.
     */
    public class ThreadTest extends Thread {
    
        private static boolean flag = false;
    
        public void run() {
            System.out.println("t1:" + Thread.currentThread().getId());
            while (!flag) {
                synchronized (Class.class) {
                }
            }
            System.out.println("quit!");
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadTest t1 = new ThreadTest();
            t1.start();
            Thread.sleep(50);
            ThreadTest.flag = true;
            System.out.println("main:" + Thread.currentThread().getId());
        }
    }  

    相对版本1,while循环中增加了一个synchronized同步代码块,虽然里面啥代码也没有,但是再次运行,能正常quit了(想下为啥?)

    答案:(也是从网上抄来的)

    synchronized关键字强制实现一个互斥锁,使得被保护的代码块在同一时间只能有一个线程进入并执行。同时也带另外一个作用:在线程进入synchronized块之前,会把工作存内存中的所有内容映射到主内存上,然后把工作内存清空再从主存储器上拷贝最新的值。而在线程退出synchronized块时,同样会把工作内存中的值映射到主内存,但此时并不会清空工作内存。这样,工作内存中的值和主内存中的值是一致的,保证了数据的一致性!(换句话说,run中的while看到了主存中的flag变量值的改变)

    思考题:如果在while{}里写一行println打印输出,即:

            while (!flag) {
                System.out.println("flag=" + flag);
            }  

    也能正常退出,留给大家去想。(提示:可以去看看println的源码实现)

    2.3 版本3

    package test.cn.mwee.order.monitor;
    
    /**
     * Created by 菩提树下的杨过 on 2017/6/11.
     */
    public class ThreadTest extends Thread {
    
        private volatile static boolean flag = false;
    
        public void run() {
            System.out.println("t1:" + Thread.currentThread().getId());
            while (!flag) {
    
            }
            System.out.println("quit!");
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadTest t1 = new ThreadTest();
            t1.start();
            Thread.sleep(50);
            ThreadTest.flag = true;
            System.out.println("main:" + Thread.currentThread().getId());
        }
    }  

    相对版本1,flag变量前加了关键字volatile,它能保证对该变量的修改,同步到其它线程,即其它线程读取flag时,看到的就是变化后的最新值,同时volatile还能防止指令重排序。运行下,也能如期打印出quit,程序退出。

    注:volatile只能保证其它线程看到的变量值是最新的,但是并不保证原子性(换句话说,高并发情况下,仍然无法100%保证线程安全)

    2.4 版本4

    package test.cn.mwee.order.monitor;
    
    import java.util.concurrent.atomic.AtomicBoolean;
    
    /**
     * Created by 菩提树下的杨过 on 2017/6/11.
     */
    public class ThreadTest extends Thread {
    
        private static AtomicBoolean flag = new AtomicBoolean(false);
    
        public void run() {
            System.out.println("t1:" + Thread.currentThread().getId());
            while (!flag.get()) {
    
            }
            System.out.println("quit!");
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadTest t1 = new ThreadTest();
            t1.start();
            Thread.sleep(50);
            ThreadTest.flag.set(true);
            System.out.println("main:" + Thread.currentThread().getId());
        }
    }  

    与上一个版本相比,使用了可以保证原子性,又不用加同步锁的并发包里的AtomicXXX系列类,同样也可以正常打印出quit,推荐使用这个。

    如果感兴趣的话,可以看下AtomicBoolean的源码,其实是借助volatile以及CAS来实现的,源码一看便知,不再啰嗦。

  • 相关阅读:
    第36课 经典问题解析三
    第35课 函数对象分析
    67. Add Binary
    66. Plus One
    58. Length of Last Word
    53. Maximum Subarray
    38. Count and Say
    35. Search Insert Position
    28. Implement strStr()
    27. Remove Element
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/6994796.html
Copyright © 2011-2022 走看看