juc并发编程02——JMM模型(下)

简介: 我们在这篇文章中将介绍JMM模型,也就是java内存模型。注意,本文所提到的JMM模型与JVM内存模型属于不同层次的内容。JVM内存模型讲的是物理内存空间的分配,而JMM则强调对于JVM内存模型的抽象。

3.volitile关键字

之前我们说了,线程并不会直接操作主内存的变量,而是操作工作内存中拷贝的变量副本。理解了这一点后,思考下面代码是有限操作还是无限操作

public class demo6 {
    private static int a = 0;
    public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                while(a == 0) {}
                System.out.println( "thread finished");
            }).start();
            Thread.sleep(1000);
            System.out.println("edit var a...");
            a = 1;
        }
}

答案是无限操作,为什么呢?当然因为新建线程中使用的不过是工作缓存中的副本咯。

现在我们把代码稍微修改下。您觉得是有限操作还是无限操作呢?

public class demo6 {
    private static int a = 0;
    public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                while (a == 0) {
                    System.out.println( "a = " +  a);
                }
            }).start();
            Thread.sleep(1000);
            System.out.println("edit var a...");
            a = 1;
        }
}

答案是有限操作,不信您可以自己试试看。为什么呢?不妨看看System.out.println()方法的源码

    public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

原来使用了synchronized关键字呀,除了保证操作的原子性外,还可以保证可见性.线程加锁时将清空工作内存的变量值,线程解锁前,将会把变量的最新值更新到主内存中,因此,它就可以看到最新的a的值为1,退出程序了。


除了synchronized,我们更多使用volitile关键字来实现可见性。


使用volitile修饰变量a即可

public class demo6 {
    private static volatile int a = 0;
    public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                while (a == 0) {
                }
                System.out.println("thread execute finished...");
            }).start();
            Thread.sleep(1000);
            System.out.println("edit var a...");
            a = 1;
        }
}

volitile修饰变量为什么可以保证可见性呢?与sychoronized加锁会让工作空间变量失效不同,它是通过禁止指令重排序实现的。其底层原理是通过内存屏障实现,它可以保证其修饰的变量在进行读写操作时,之前的指令已经执行完,之后的指令还未执行,即操作volitile修饰变量的语句相对位置不会发生改变。

4.happens-before原则

前面我们已经了解了指令重排序的优缺点,JVM提出了happens-before(先行发生)原则,确保程序员只要按照原则编程,就能保证并发编程的正确性。


同一个线程内,前面操作happens-before后面的操作。即使可能出现指令重排,但是前面语句对于变量的修改一定对后续操作可见。

对于一个锁的解锁操作,happens-before对于这把锁后续的加锁操作。即前一个线程解锁后,后面线程都能看到该锁对于变量修改的结果,实际上,我们前面的举例就说了,synchorinized关键字在解锁时会把变量更新到主内存中。

volitile变量的写操作happens-before后续对这个变量的读操作。

线程启动原则。线程A启动线程B,在线程B中可以看到线程B启动前线程A的操作。

线程加入原则。线程A执行过程中join线程B,并成功返回。则线程B的操作happens-before线程A。

传递性规则。如果Ahappens-beforeB,Bhappens-beforeC,则Ahappens-beforeC.

上面的原则如果理解了就很容易掌握。基于上述原则,看如下代码。

public class Demo7 {
    private static int a = 0;
    private static int b = 0;
    public static void main(String[] args) {
        a = 10;
        b = a + 1;
        new Thread(() -> {
            if(b > 10) {
                System.out.println(a);
            }
        }
        ).start();
    }
}

答案是10.

相关文章
|
6月前
|
缓存 Java 编译器
JUC 并发编程之JMM
Java内存模型是Java虚拟机(JVM)规范中定义的一组规则,用于屏蔽各种硬件和操作系统的内存访问差异,保证多线程情况下程序的正确执行。Java内存模型规定了线程之间如何交互以及线程和内存之间的关系。它主要解决的问题是可见性、原子性和有序性。
|
6月前
|
Java 程序员 开发者
深入理解Java并发编程:线程同步与锁机制
【4月更文挑战第30天】 在多线程的世界中,确保数据的一致性和线程间的有效通信是至关重要的。本文将深入探讨Java并发编程中的核心概念——线程同步与锁机制。我们将从基本的synchronized关键字开始,逐步过渡到更复杂的ReentrantLock类,并探讨它们如何帮助我们在多线程环境中保持数据完整性和避免常见的并发问题。文章还将通过示例代码,展示这些同步工具在实际开发中的应用,帮助读者构建对Java并发编程深层次的理解。
|
3月前
|
存储 Java
JUC(8)JMM
这篇文章介绍了Java内存模型(JMM),解释了volatile关键字的作用,包括确保变量的可见性、禁止指令重排但不保证操作的原子性,并探讨了单例模式的实现方式,包括饿汉式和懒汉式单例模式的示例代码。
JUC(8)JMM
|
存储 缓存 安全
JUC第二讲:Java并发理论基础:Java内存模型(JMM)与线程
JUC第二讲:Java并发理论基础:Java内存模型(JMM)与线程
109 0
|
缓存 安全 Java
【Java并发编程 二】JMM内存模型(三)
【Java并发编程 二】JMM内存模型
104 0
|
存储 缓存 安全
【Java并发编程 二】JMM内存模型(一)
【Java并发编程 二】JMM内存模型(一)
227 0
|
存储 Java 调度
并发编程(二)JMM模型
并发编程(二)JMM模型
122 0
JUC并发编程学习(九)-读写锁
JUC并发编程学习(九)-读写锁
JUC并发编程学习(九)-读写锁
|
存储 缓存 Java
JUC并发编程学习(十六)谈谈java内存模型JMM
JUC并发编程学习(十六)谈谈java内存模型JMM
JUC并发编程学习(十六)谈谈java内存模型JMM
|
缓存 Java 编译器
juc并发编程02——JMM模型(上)
我们在这篇文章中将介绍JMM模型,也就是java内存模型。注意,本文所提到的JMM模型与JVM内存模型属于不同层次的内容。JVM内存模型讲的是物理内存空间的分配,而JMM则强调对于JVM内存模型的抽象。
juc并发编程02——JMM模型(上)