精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2013-12-18
iamiwell 写道 qiaoenxin 写道 iamiwell 写道 qiaoenxin 写道 iamiwell 写道 看来楼主还是不理解为什么要加volatile,简单说,你所说的那种方式,理论上也不是线程安全的,注意new的几个过程
看来是你没有真正理解new的过程。 A a = new A(); ...//注意后面代码 new的过程只会影响a的赋值先后顺序,这是因为new的过程不是一个原子造成的。但是不管new的过程有多少,new语句后面的代码都不会在new的过程结束之前执被行。 另外还必须考虑虚拟机优化问题,这里就不仔细说了。 ... 20: new #3; //class Singleton 23: dup 24: invokespecial #4; //Method "<init>":()V <assigns> ... new执行之后,this地址即已经可以使用了,但是构造工作并没有全部完成,在这种情况下, 不管怎么去判断和赋值,都不能保证最终拿到的instance是已经构造好了 至于你的try,这个是防止java编译的优化,而“但是不管new的过程有多少,new语句后面的代码都不会在new的过程结束之前执被行。 ”这句话,是没有根据的,其实你描述的就是一个屏障,没有规范说每个构造器都必须添加屏障,这也是为什么可以使用volatile或final,因为它们就是一个屏障,正如你所说,你也认同构造的过程并不是原子的么 总的来说,理论上这种方式不是线程安全的,但实际中,有可能某些处理器的特殊性,能够保证它的线程安全,或者发生线程安全问题的场景基本不可见 也可以看看类似的文章 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html or http://ifeve.com/double-checked-locking-with-delay-initialization/ 你的问题7楼已经给你解释了。你需要注意temp变量的作用。 至于你给出的文章,我刚学java的时候就已经看过内容相似的文章。起初我也和你的想法一样,后来相关文章也拜读了不少,才发现那篇文章的问题。 对于A a = new A(); 这个过程大致做了3件事情 1.给实例分配内存。 2.初始实例的构造器 3.将变量指向实例对象的内存空间。 我姑且称为new的三个过程为一个执行单元unit(1,2,3)。虚拟机总是尽可能多的将一个执行单元中可以并发执行的元素(1,2,3)同时提交给cpu乱序执行,提高执行效率。 哎,没什么话好说了,请注意,已经拿到this的地址了,当然可以直接给temp,这个时候temp也不是null,也可以赋值给singleton,但这整个过程中,都不能保证构造函数执行完成,这种问题其实讨论的也没什么意思,我只是好心指出来而已 按照你的说法 A a = new A(); a.method();//按照你的说法这里不能保证a的构造函数执行完成 a.method()是要报空指针异常了? 如果真如你说,如上代码等于如下代码 a.method(); A a = new A(); 先new实例再使用还有意义?你的程序随时的都报异常了。 你好好理解我说的一个执行单元的概念,一个执行单元没有结束之前下执行单元是不会执行的。 |
|
返回顶楼 | |
发表时间:2013-12-18
最后修改:2013-12-18
iamiwell 写道 qiaoenxin 写道 iamiwell 写道 qiaoenxin 写道 iamiwell 写道 看来楼主还是不理解为什么要加volatile,简单说,你所说的那种方式,理论上也不是线程安全的,注意new的几个过程
看来是你没有真正理解new的过程。 A a = new A(); ...//注意后面代码 new的过程只会影响a的赋值先后顺序,这是因为new的过程不是一个原子造成的。但是不管new的过程有多少,new语句后面的代码都不会在new的过程结束之前执被行。 另外还必须考虑虚拟机优化问题,这里就不仔细说了。 ... 20: new #3; //class Singleton 23: dup 24: invokespecial #4; //Method "<init>":()V <assigns> ... new执行之后,this地址即已经可以使用了,但是构造工作并没有全部完成,在这种情况下, 不管怎么去判断和赋值,都不能保证最终拿到的instance是已经构造好了 至于你的try,这个是防止java编译的优化,而“但是不管new的过程有多少,new语句后面的代码都不会在new的过程结束之前执被行。 ”这句话,是没有根据的,其实你描述的就是一个屏障,没有规范说每个构造器都必须添加屏障,这也是为什么可以使用volatile或final,因为它们就是一个屏障,正如你所说,你也认同构造的过程并不是原子的么 总的来说,理论上这种方式不是线程安全的,但实际中,有可能某些处理器的特殊性,能够保证它的线程安全,或者发生线程安全问题的场景基本不可见 也可以看看类似的文章 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html or http://ifeve.com/double-checked-locking-with-delay-initialization/ 你的问题7楼已经给你解释了。你需要注意temp变量的作用。 至于你给出的文章,我刚学java的时候就已经看过内容相似的文章。起初我也和你的想法一样,后来相关文章也拜读了不少,才发现那篇文章的问题。 对于A a = new A(); 这个过程大致做了3件事情 1.给实例分配内存。 2.初始实例的构造器 3.将变量指向实例对象的内存空间。 我姑且称为new的三个过程为一个执行单元unit(1,2,3)。虚拟机总是尽可能多的将一个执行单元中可以并发执行的元素(1,2,3)同时提交给cpu乱序执行,提高执行效率。 哎,没什么话好说了,请注意,已经拿到this的地址了,当然可以直接给temp,这个时候temp也不是null,也可以赋值给singleton,但这整个过程中,都不能保证构造函数执行完成,这种问题其实讨论的也没什么意思,我只是好心指出来而已 你再仔细想想吧。 try 块前的代码 和 try块后的代码 会发生指令重排吗!? 楼主都已经在注释里指出了,这里用个try就是专门为了避免由虚拟机激进优化而产生的CPU指令重排。 你看看仔细。 据我目前的知识,JVM现在还不能做逃逸分析,有try就不会在发生重排。 如果你认为try block无法阻止指令重排的话,请给出证据。 间接证据: if (temp != null) 是在运行线程内,如果像你说的重排发生,会破坏线程内程序顺序执行的逻辑。 线程内程序顺序执行是JVM的基本保障,跟多线程可见性什么的都没关系了。 |
|
返回顶楼 | |
发表时间:2013-12-18
beowulf2005 写道 iamiwell 写道 qiaoenxin 写道 iamiwell 写道 qiaoenxin 写道 iamiwell 写道 看来楼主还是不理解为什么要加volatile,简单说,你所说的那种方式,理论上也不是线程安全的,注意new的几个过程
看来是你没有真正理解new的过程。 A a = new A(); ...//注意后面代码 new的过程只会影响a的赋值先后顺序,这是因为new的过程不是一个原子造成的。但是不管new的过程有多少,new语句后面的代码都不会在new的过程结束之前执被行。 另外还必须考虑虚拟机优化问题,这里就不仔细说了。 ... 20: new #3; //class Singleton 23: dup 24: invokespecial #4; //Method "<init>":()V <assigns> ... new执行之后,this地址即已经可以使用了,但是构造工作并没有全部完成,在这种情况下, 不管怎么去判断和赋值,都不能保证最终拿到的instance是已经构造好了 至于你的try,这个是防止java编译的优化,而“但是不管new的过程有多少,new语句后面的代码都不会在new的过程结束之前执被行。 ”这句话,是没有根据的,其实你描述的就是一个屏障,没有规范说每个构造器都必须添加屏障,这也是为什么可以使用volatile或final,因为它们就是一个屏障,正如你所说,你也认同构造的过程并不是原子的么 总的来说,理论上这种方式不是线程安全的,但实际中,有可能某些处理器的特殊性,能够保证它的线程安全,或者发生线程安全问题的场景基本不可见 也可以看看类似的文章 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html or http://ifeve.com/double-checked-locking-with-delay-initialization/ 你的问题7楼已经给你解释了。你需要注意temp变量的作用。 至于你给出的文章,我刚学java的时候就已经看过内容相似的文章。起初我也和你的想法一样,后来相关文章也拜读了不少,才发现那篇文章的问题。 对于A a = new A(); 这个过程大致做了3件事情 1.给实例分配内存。 2.初始实例的构造器 3.将变量指向实例对象的内存空间。 我姑且称为new的三个过程为一个执行单元unit(1,2,3)。虚拟机总是尽可能多的将一个执行单元中可以并发执行的元素(1,2,3)同时提交给cpu乱序执行,提高执行效率。 哎,没什么话好说了,请注意,已经拿到this的地址了,当然可以直接给temp,这个时候temp也不是null,也可以赋值给singleton,但这整个过程中,都不能保证构造函数执行完成,这种问题其实讨论的也没什么意思,我只是好心指出来而已 你再仔细想想吧。 try 块前的代码 和 try块后的代码 会发生指令重排吗!? 楼主都已经在注释里指出了,这里用个try就是专门为了避免由虚拟机激进优化而产生的CPU指令重排。 你看看仔细。 据我目前的知识,JVM现在还不能做逃逸分析,有try就不会在发生重排。 如果你认为try block无法阻止指令重排的话,请给出证据。 间接证据: if (temp != null) 是在运行线程内,如果像你说的重排发生,会破坏线程内程序顺序执行的逻辑。 线程内程序顺序执行是JVM的基本保障,跟多线程可见性什么的都没关系了。 变成口水仗了,关键在于什么情况下是可以重排的,什么情况是不可以重排的,对于有数据依赖的是不可以重排,或者主动添加屏障,temp和singleton只是依赖对象的地址,用于赋值,它并没有依赖其它,而构造器也只是依赖对象地址,最终这两者没有数据依赖,理论上是可以重排的,至于说的try,catch,难道对于try, catch,有屏障的作用么,貌似还没听说过,也许我孤陋寡闻了 |
|
返回顶楼 | |
发表时间:2013-12-18
最后修改:2013-12-18
iamiwell 写道 beowulf2005 写道 iamiwell 写道 qiaoenxin 写道 iamiwell 写道 qiaoenxin 写道 iamiwell 写道 看来楼主还是不理解为什么要加volatile,简单说,你所说的那种方式,理论上也不是线程安全的,注意new的几个过程
看来是你没有真正理解new的过程。 A a = new A(); ...//注意后面代码 new的过程只会影响a的赋值先后顺序,这是因为new的过程不是一个原子造成的。但是不管new的过程有多少,new语句后面的代码都不会在new的过程结束之前执被行。 另外还必须考虑虚拟机优化问题,这里就不仔细说了。 ... 20: new #3; //class Singleton 23: dup 24: invokespecial #4; //Method "<init>":()V <assigns> ... new执行之后,this地址即已经可以使用了,但是构造工作并没有全部完成,在这种情况下, 不管怎么去判断和赋值,都不能保证最终拿到的instance是已经构造好了 至于你的try,这个是防止java编译的优化,而“但是不管new的过程有多少,new语句后面的代码都不会在new的过程结束之前执被行。 ”这句话,是没有根据的,其实你描述的就是一个屏障,没有规范说每个构造器都必须添加屏障,这也是为什么可以使用volatile或final,因为它们就是一个屏障,正如你所说,你也认同构造的过程并不是原子的么 总的来说,理论上这种方式不是线程安全的,但实际中,有可能某些处理器的特殊性,能够保证它的线程安全,或者发生线程安全问题的场景基本不可见 也可以看看类似的文章 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html or http://ifeve.com/double-checked-locking-with-delay-initialization/ 你的问题7楼已经给你解释了。你需要注意temp变量的作用。 至于你给出的文章,我刚学java的时候就已经看过内容相似的文章。起初我也和你的想法一样,后来相关文章也拜读了不少,才发现那篇文章的问题。 对于A a = new A(); 这个过程大致做了3件事情 1.给实例分配内存。 2.初始实例的构造器 3.将变量指向实例对象的内存空间。 我姑且称为new的三个过程为一个执行单元unit(1,2,3)。虚拟机总是尽可能多的将一个执行单元中可以并发执行的元素(1,2,3)同时提交给cpu乱序执行,提高执行效率。 哎,没什么话好说了,请注意,已经拿到this的地址了,当然可以直接给temp,这个时候temp也不是null,也可以赋值给singleton,但这整个过程中,都不能保证构造函数执行完成,这种问题其实讨论的也没什么意思,我只是好心指出来而已 你再仔细想想吧。 try 块前的代码 和 try块后的代码 会发生指令重排吗!? 楼主都已经在注释里指出了,这里用个try就是专门为了避免由虚拟机激进优化而产生的CPU指令重排。 你看看仔细。 据我目前的知识,JVM现在还不能做逃逸分析,有try就不会在发生重排。 如果你认为try block无法阻止指令重排的话,请给出证据。 间接证据: if (temp != null) 是在运行线程内,如果像你说的重排发生,会破坏线程内程序顺序执行的逻辑。 线程内程序顺序执行是JVM的基本保障,跟多线程可见性什么的都没关系了。 变成口水仗了,关键在于什么情况下是可以重排的,什么情况是不可以重排的,对于有数据依赖的是不可以重排,或者主动添加屏障,temp和singleton只是依赖对象的地址,用于赋值,它并没有依赖其它,而构造器也只是依赖对象地址,最终这两者没有数据依赖,理论上是可以重排的,至于说的try,catch,难道对于try, catch,有屏障的作用么,貌似还没听说过,也许我孤陋寡闻了 纠结其实就在这句 由于try的存在,虚拟机无法优化temp!=null 是否为true 对于Java 6之前JIT,成立还是不成立,你我都没源码,也没证据。 如果如楼主所言,JIT放弃优化,那么也没内存屏障什么事儿了。 我认为,我写编译器的话,就不会考虑在遇到分支语句时做过于激进的优化。 因为有分支存在,重排指令顺序对性能的提升,恐怕抵不过一次错误的预判对性能的损失。 而且这种只跑一次的代码块,基本上没可能被JIT选上,并优化。 |
|
返回顶楼 | |
发表时间:2013-12-18
使用volatile也没用。volatile只是对原子操作的并发有效。
LZ的try catch 或许有效,代码可读性貌似差了点。 // 延迟方法1 public class Singleton { private static Singleton instance; private static boolean init = false; private Singleton() { }; /** * @return */ public static Singleton getInstance() { if ( !init ) { synchronized (Singleton.class) { if ( !init ) { instance = new Singleton(); init = true; } } } return instance; } } // 延迟方法2 public class Singleton { private static Singleton instance; private Singleton() { }; /** * @return */ private static class SingletonHolder { public final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } } |
|
返回顶楼 | |
发表时间:2013-12-25
samm 写道 使用volatile也没用。volatile只是对原子操作的并发有效。
LZ的try catch 或许有效,代码可读性貌似差了点。 // 延迟方法1 public class Singleton { private static Singleton instance; private static boolean init = false; private Singleton() { }; /** * @return */ public static Singleton getInstance() { if ( !init ) { synchronized (Singleton.class) { if ( !init ) { instance = new Singleton(); init = true; } } } return instance; } } // 延迟方法2 public class Singleton { private static Singleton instance; private Singleton() { }; /** * @return */ private static class SingletonHolder { public final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } } 第一个不加 volatile 是错误的,因为多个线程中的init可能并不是一个值(不能保证可见性)。 其实JAVA的双重检查加锁实现的单例模式已经是不建议使用的方法了。 第二个方法是《Java 并发编程实战》建议使用的方法。 |
|
返回顶楼 | |
发表时间:2013-12-29
// 延迟方法1
public class Singleton { private static Singleton instance; ---------- 确实漏了volatile。 |
|
返回顶楼 | |