`
vanadiumlin
  • 浏览: 504899 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

java 内存可见性

 
阅读更多
  1. /**
  2. * 可见性问题
  3. * @author Snway
  4. *
  5. */
  6. publicclass Visibility {
  7. privatestaticboolean stop;
  8. publicstaticvoid main(String[] args) throws Exception {
  9. Thread t1 = new Thread(new Runnable(){
  10. publicvoid run() {
  11. int i = 0;
  12. while(!stop) {
  13. i++;
  14. }
  15. System.out.println("finish loop,i=" + i);
  16. }
  17. });
  18. t1.start();
  19. Thread.sleep(1000);
  20. stop = true;
  21. Thread.sleep(2000);
  22. System.out.println("finish main");
  23. }
  24. }
/**
 * 可见性问题
 * @author Snway
 *
 */
public class Visibility {
    
    private static boolean stop;
    
    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(new Runnable(){
            public void run() {
                int i = 0;
                while(!stop) {
                    i++;
                }
                System.out.println("finish loop,i=" + i);
            }
        });
        t1.start();
        Thread.sleep(1000);
        stop = true;
        Thread.sleep(2000);
        System.out.println("finish main");
    }
}

 

 

在hotspot中编译后分别运行:
1、java Visibility
2、java -server Visibility

会发现第二种方式会死循环!

可见性是关于在哪些情况下,一个线程执行的结果对另一个线程是可见的问题。在本例中的问题正是由于主线程对stop变量的写入操作结果,对t1线程是不可见的所导致的。在单线程中,如果向某个变量先写入值,然后再没有其他写入操作的情况下读取这个变量,那么总能得到相同的值。但是,当读操作与写操作在不同的线程中执行时,情况却并非如此。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。在本例中,可对声明变量stop时加volatile修正可见性问题。

只有在下列情况时,一个线程对字段的修改才能确保对另一个线程可见:

一个写线程释放一个锁之后,另一个读线程随后获取了同一个锁。本质上,线程释放锁时会将强制刷新工作内存中的脏数据到主内存中,获取一个锁将强制线程装载(或重新装载)字段的值。锁提供对一个同步方法或块的互斥性执行,线程执行获取锁和释放锁时,所有对字段的访问的内存效果都是已定义的。

注意同步的双重含义:锁提供高级同步协议,同时在线程执行同步方法或块时,内存系统(有时通过内存屏障指令)保证值的一致性。这说明,与顺序程序设计相比较,并发程序设计与分布式程序设计更加类似。同步的第二个特性可以视为一种机制:一个线程在运行已同步方法时,它将发送和/或接收其他线程在同步方法中对变量所做的修改。从这一点来说,使用锁和发送消息仅仅是语法不同而已。

如果把一个字段声明为volatile型,线程对这个字段写入后,在执行后续的内存访问之前,线程必须刷新这个字段且让这个字段对其他线程可见(即该字段立即刷新)。每次对volatile字段的读访问,都要重新装载字段的值。

一个线程首次访问一个对象的字段,它将读到这个字段的初始值或被某个线程写入后的值。
此外,把还未构造完成的对象的引用暴露给某个线程,这是一个错误的做法 (see ?.1.2)。在构造函数内部开始一个新线程也是危险的,特别是这个类可能被子类化时。Thread.start有如下的内存效果:调用start方法的线程释放了锁,随后开始执行的新线程获取了这个锁。如果在子类构造函数执行之前,可运行的超类调用了new Thread(this).start(),当run方法执行时,对象很可能还没有完全初始化。同样,如果你创建且开始一个新线程T,这个线程使用了在执行start之后才创建的一个对象X。你不能确信X的字段值将能对线程T可见。除非你把所有用到X的引用的方法都同步。如果可行的话,你可以在开始T线程之前创建X。

线程终止时,所有写过的变量值都要刷新到主内存中。比如,一个线程使用Thread.join来终止另一个线程,那么第一个线程肯定能看到第二个线程对变量值得修改。

注意,在同一个线程的不同方法之间传递对象的引用,永远也不会出现内存可见性问题。
内存模型确保上述操作最终会发生,一个线程对一个特定字段的特定更新,最终将会对其他线程可见,但这个“最终”可能是很长一段时间。线程之间没有同步时,很难保证对字段的值能在多线程之间保持一致(指写线程对字段的写入立即能对读线程可见)。特别是,如果字段不是volatile或没有通过同步来访问这个字段,在一个循环中等待其他线程对这个字段的写入,这种情况总是错误的(see ?.2.6)。

在缺乏同步的情况下,模型还允许不一致的可见性。比如,得到一个对象的一个字段的最新值,同时得到这个对象的其他字段的过期的值。同样,可能读到一个引用变量的最新值,但读取到这个引用变量引用的对象的字段的过期值。
不管怎样,线程之间的可见性并不总是失效(指线程即使没有使用同步,仍然有可能读取到字段的最新值),内存模型仅仅是允许这种失效发生而已。因此,即使多个线程之间没有使用同步,也不保证一定会发生内存可见性问题(指线程读取到过期的值),java内存模型仅仅是允许内存可见性问题发生而已。在很多当前的JVM实现和java执行平台中,甚至是在那些使用多处理器的JVM和平台中,也很少出现内存可见性问题。共享同一个CPU的多个线程使用公共的缓存,缺少强大的编译器优化,以及存在强缓存一致性的硬件,这些都会使线程更新后的值能够立即在多线程之间传递。这使得测试基于内存可见性的错误是不切实际的,因为这样的错误极难发生。或者这种错误仅仅在某个你没有使用过的平台上发生,或仅在未来的某个平台上发生。这些类似的解释对于多线程之间的内存可见性问题来说非常普遍。没有同步的并发程序会出现很多问题,包括内存一致性问题。

 

参考文献:

《Java并发编程实战》第三章

分享到:
评论

相关推荐

    Java 内存模型

    Java内存模型的核心内容涵盖了锁、线程间的交互、内存可见性和顺序一致性等方面。在JSR-133之前的Java内存模型规范中,volatile变量的语义较弱,它们的访问可以自由排序。但在新规范中,volatile变量的语义被加强为...

    深入理解Java内存模型

    由于本地内存的存在,JMM需要控制主内存与每个线程的本地内存之间的交互,以确保内存可见性。 Java内存模型中的重排序是一个重要组成部分,它分为编译器重排序、指令级并行的重排序以及内存系统的重排序。编译器...

    深入理解Java内存模型 pdf 超清版

    - 描述了两个操作之间的顺序关系,是JMM保证内存可见性的基础。 - 例如,一个线程初始化一个对象,然后另一个线程访问这个对象,Happens-Before原则确保了初始化操作对其他线程可见。 4. **重排序** - 编译器和...

    深入理解 Java 内存模型

    Java 内存模型(Java Memory Model,简称 JMM)是 Java 平台中关于线程如何访问共享变量的一套规则,它定义了线程之间的内存可见性、数据一致性以及指令重排序等关键概念,对于多线程编程和并发性能优化至关重要。...

    java内存模型文档

    - 这是判断数据是否存在竞争、是否需要同步的一个依据,规定了内存可见性的顺序。 7. **原子操作与CAS** - 原子操作(如AtomicInteger)在不使用锁的情况下保证了更新操作的原子性。 - CAS(Compare and Swap)...

    深度剖析java内存模型

    理解Java内存模型对于编写多线程程序具有重要意义,因为这关系到内存可见性问题。如果程序员不理解线程间通信的隐式机制,可能会遇到线程安全问题,如数据竞争或条件竞争,导致程序行为不确定。因此,Java内存模型是...

    Java内存模型--原子性;有序性;可见性1

    总结来说,Java内存模型通过原子性、有序性和可见性保证了多线程环境下的数据一致性。理解并熟练运用这些概念是编写高效、线程安全的Java代码的基础。在实际开发中,应根据需求选择合适的方式来确保这三个特性,以...

    深入理解 Java 内存模型_程晓明_InfoQ_java_内存模型_

    Java内存模型,简称JMM(Java Memory Model),是Java编程语言规范的一部分,它定义了线程如何共享和访问内存,以及在多线程环境中如何保证数据一致性。理解JMM对于编写高效、正确且线程安全的Java代码至关重要。 ...

    Java volatile与AQS锁内存可见性

    从JUC中的AQS引入,讲解Java volatile与AQS锁内存可见性

    cpu 内存模型和java内存模型

    volatile关键字是Java内存模型中一个非常重要的概念,它保证了变量的可见性,即任何线程对该变量的修改都会立即被其它线程得知,但并不保证操作的原子性。 ### 锁机制 在Java中,锁是一种同步机制,用来控制多个...

    java内存模型.pdf

    - JSR133:JSR133是Java内存模型的重要修订,旨在修复早期JMM存在的问题,如内存可见性问题,提供更强的内存语义,并提高性能。它引入了对final和synchronized的新规则,以确保正确同步的代码行为明确且直观。 2. ...

    《深入理解JAVA内存模型》PDF

    局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的...

    java内存模型详解--非常经典

    Java内存模型(JVM Memory Model,简称JMM)是Java平台中的一个重要概念,它定义了程序中各个变量的访问规则,以及在多线程环境下的内存一致性效果。JMM主要解决的是并发环境下不同线程之间如何共享数据以及如何保证...

    Java内存模型详解JMM.docx

    Java内存模型详解JMM Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)中的一种内存模型,它描述了程序中各个变量之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节...

    java内存模型详解

    Java内存模型,简称JMM(Java Memory Model),是Java虚拟机规范中定义的一个抽象概念,它规定了程序中各个线程如何访问共享变量,以及对这些访问进行同步控制的规则。理解Java内存模型对于编写多线程并发程序至关...

    深入理解java内存模型

    4. ** volatile 变量**:Java中的volatile关键字能确保共享变量的可见性和有序性。当一个变量被volatile修饰后,其修改会立即反映到其他线程的工作内存中,避免了数据的不一致。 5. **synchronized**:synchronized...

    Java理论与实践:修复Java内存模型1

    Java内存模型(Java Memory Model, JMM)是Java平台中用于规范线程间通信和内存可见性的重要概念,它的目标是确保多线程环境下的正确同步。然而,原始的JMM存在一些严重的缺陷,导致了开发者在理解和实现线程安全时...

    Java内存模型分析与其在编程中的应用.pdf

    在多线程编程中,Java内存模型涉及到线程间的内存可见性问题,即当一个线程修改了对象的共享变量后,其他线程何时能“看到”这个修改。Java内存模型通过提供synchronized关键字和volatile变量来实现线程间通信和内存...

Global site tag (gtag.js) - Google Analytics