解决方案 »

  1.   

    试了好几次,把String s = new String();去掉,程序依然会在一秒后退出循环。楼主?????
      

  2.   

    不知道2楼环境是什么?我的JDK 1.8,Netbeans 8.0.1,并且不管是在IDE下,还是独立编译后运行都是这种情况,循环后输出没有执行,程序不能退出。然而在调试时,将断点设在循环后的输出语句,程序运行正常
      

  3.   

    不好意思,可能是我表述不清楚。是指当有String s = new String("");时,程序将1秒后将exec设置为false,从而结束线程的循环,然后输出"退出了循环",程序结束。这种运行是属于正常的运行,程序的逻辑就是要求这样运行。然而当没有String s = new String("");这句时,"退出了循环"这句将不执行,且程序不会自动结束,属于异常情况了。正确的运行方式应该与有String s = new String("");这句时相同才是正确的。
      

  4.   

    不好意思,可能是我表述不清楚。是指当有String s = new String("");时,程序将1秒后将exec设置为false,从而结束线程的循环,然后输出"退出了循环",程序结束。这种运行是属于正常的运行,程序的逻辑就是要求这样运行。然而当没有String s = new String("");这句时,"退出了循环"这句将不执行,且程序不会自动结束,属于异常情况了。正确的运行方式应该与有String s = new String("");这句时相同才是正确的。
      

  5.   

    这是Java线程并发性中存在的可见性问题。所谓可见性问题,是指是关于在哪些情况下,一个线程执行的结果对另一个线程是可见的问题。在你的例子中产生的问题是由于主线程对exec变量的写入操作结果,对it线程是不可见的所导致的。在单线程中,如果向某个变量先写入值,然后再没有其他写入操作的情况下读取这个变量,那么总能得到相同的值。但是,当读操作与写操作在不同的线程中执行时,情况却并非如此。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。在本例中,可对声明变量exec时加上volatile修正可见性问题。     
            只有在下列情况时,一个线程对字段的修改才能确保对另一个线程可见:
            一个写线程释放一个锁之后,另一个读线程随后获取了同一个锁。本质上,线程释放锁时会将强制刷新工作内存中的脏数据到主内存中,获取一个锁将强制线程装载(或重新装载)字段的值。锁提供对一个同步方法或块的互斥性执行,线程执行获取锁和释放锁时,所有对字段的访问的内存效果都是已定义的。
            注意同步的双重含义:锁提供高级同步协议,同时在线程执行同步方法或块时,内存系统(有时通过内存屏障指令)保证值的一致性。这说明,与顺序程序设计相比较,并发程序设计与分布式程序设计更加类似。同步的第二个特性可以视为一种机制:一个线程在运行已同步方法时,它将发送和/或接收其他线程在同步方法中对变量所做的修改。从这一点来说,使用锁和发送消息仅仅是语法不同而已。
           如果把一个字段声明为volatile型,线程对这个字段写入后,在执行后续的内存访问之前,线程必须刷新这个字段且让这个字段对其他线程可见(即该字段立即刷新)。每次对volatile字段的读访问,都要重新装载字段的值。
            一个线程首次访问一个对象的字段,它将读到这个字段的初始值或被某个线程写入后的值。
            此外,把还未构造完成的对象的引用暴露给某个线程,这是一个错误的做法。在构造函数内部开始一个新线程也是危险的,特别是这个类可能被子类化时。Thread.start有如下的内存效果:调用start方法的线程释放了锁,随后开始执行的新线程获取了这个锁。如果在子类构造函数执行之前,可运行的超类调用了new Thread(this).start(),当run方法执行时,对象很可能还没有完全初始化。同样,如果你创建且开始一个新线程T,这个线程使用了在执行start之后才创建的一个对象X。你不能确信X的字段值将能对线程T可见。除非你把所有用到X的引用的方法都同步。如果可行的话,你可以在开始T线程之前创建X。
            线程终止时,所有写过的变量值都要刷新到主内存中。比如,一个线程使用Thread.join来终止另一个线程,那么第一个线程肯定能看到第二个线程对变量值得修改。
            注意,在同一个线程的不同方法之间传递对象的引用,永远也不会出现内存可见性问题。
    内存模型确保上述操作最终会发生,一个线程对一个特定字段的特定更新,最终将会对其他线程可见,但这个“最终”可能是很长一段时间。线程之间没有同步时,很难保证对字段的值能在多线程之间保持一致(指写线程对字段的写入立即能对读线程可见)。特别是,如果字段不是volatile或没有通过同步来访问这个字段,在一个循环中等待其他线程对这个字段的写入,这种情况总是错误的。
            在缺乏同步的情况下,模型还允许不一致的可见性。比如,得到一个对象的一个字段的最新值,同时得到这个对象的其他字段的过期的值。同样,可能读到一个引用变量的最新值,但读取到这个引用变量引用的对象的字段的过期值。
            不管怎样,线程之间的可见性并不总是失效(指线程即使没有使用同步,仍然有可能读取到字段的最新值),内存模型仅仅是允许这种失效发生而已。因此,即使多个线程之间没有使用同步,也不保证一定会发生内存可见性问题(指线程读取到过期的值),java内存模型仅仅是允许内存可见性问题发生而已。在很多当前的JVM实现和java执行平台中,甚至是在那些使用多处理器的JVM和平台中,也很少出现内存可见性问题。共享同一个CPU的多个线程使用公共的缓存,缺少强大的编译器优化,以及存在强缓存一致性的硬件,这些都会使线程更新后的值能够立即在多线程之间传递。这使得测试基于内存可见性的错误是不切实际的,因为这样的错误极难发生。或者这种错误仅仅在某个你没有使用过的平台上发生,或仅在未来的某个平台上发生。这些类似的解释对于多线程之间的内存可见性问题来说非常普遍。没有同步的并发程序会出现很多问题,包括内存一致性问题。
      

  6.   

    谢谢,看完您的回复,受益非浅。我也试了原子修饰符,是能够按流程走完程序。只是,不明白的地方是,为什么在没有原子修饰符的只要在循环内部存在类对象(只是不是基本数据类型)操作就可以正常结束程序?从时间上看,与线程的对象的创建时间无关,主函数中的sleep函数的时间并不影响执行过程。如将run函数修改成
    int c = 0;
                List<String> t = new ArrayList<>();
                while (exec) {
                    t.add("");
                    c++;
                }
                System.out.println("退出了循环");不管exec变量是否以原子操作进行修饰,都能够按预期执行
      

  7.   

    可能的原因:JVM内部优化机制导致了可见性问题。(因为没有添加其他非基本类型的操作时,while循环执行的时间非常短,而JVM可能在对待读取的exec值时,是根据循环的次数做了一个统计,发现若干次循环执行的结果exec值都是不变的,结果就做了一个优化,对exec进行了内联处理(即不对exec进行读取,而是用true值替代exec)。加上volatile是告诉JVM这个变量是不稳定的,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。而在while里面添加一个非基本类型的数据操作,使得这个循环的操作时间相对拉长了,在JVM进行优化的处理时,这个时候进行统计,发现exec是发生改变的,而不做前述的内联处理了,因而效果跟用volatile关键字一样。
      

  8.   

    编译器优化导致的逻辑问题,以前用C#的时候遇到过,谢谢。不过,还有一个问题,在Netbeans下进行调试时,如果将断点设置在System.out.println("退出了循环");,程序运行正常。但不设断点时,即使是在调试模式下也是一样的不执行这句(按上述,就是循环没有退出)。以前在C#下时在调试模式下按正常逻辑走(应该是没有什么),在release下错误。怀疑JAVA编译器不区分调试和release,都进行了优化
      

  9.   

    是64位JDK的问题,32位JDK下程序正常运行