一篇讲双锁检测的文章

来源:互联网 发布:慈溪市司法拍卖淘宝网 编辑:程序博客网 时间:2024/06/14 20:23

问题来源于群里面一个同学发了一篇关于双锁检测的文章,然后就有人反对说,双锁检测是过时的。于是乎,我也去找了一下相关的文章。http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html,这一篇文,就是发到群里面的文章。

虽然这篇文章说了在1.5之后,可以使用final字段让对象完全初始化。但是,并没有说明,如果我们使用普通的成员变量,会不会造成对象半初始化对象。这里有一个共同的前提,就是这个被初始化对象是volatile类型。当时本人就给定了一个结论说如果被初始化对象包含普通类型字段(非final和volatile),那么,双锁检测一定是失败的,造成未完全初始化的对象。所以当时这个结论就错了,参考这篇文章http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html对新java内存模型的解读


Under the old memory model, accesses to volatile variables could not be reordered with each other, but they could be reordered with nonvolatile variable accesses. This undermined the usefulness of volatile fields as a means of signaling conditions from one thread to another.

这句话说明,在老的内存模型中,volatile是不能阻止对非volatile变量操作的重排序,从而volatile变量不能作为一种用于线程间通信的信号条件(这尼玛,怎么翻译,看代码就理解了)。

Under the new memory model, it is still true that volatile variables cannot be reordered with each other. The difference is that it is now no longer so easy to reorder normal field accesses around them. Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.

然后,这句话就说了,volatile对其他的非volatile变量也有可见性保证,于是,当使用volatile的时候,用于线程间的通信的信号条件,我们可以安全的使用双锁检测了。具体能够搜索的相关资料包括:jsr133(happen before,重排序,可见性)。
 

故事永远不是这么简单的结束的,结束实际上来源于两段诡异的代码。大致意思是,一个线程不断的创建ArrayList赋值给一个全局变量,另外几个线程同时读取这个ArrayList并调用add方法,那么在-server的jit编译下,会导致nullPointexception,如果对这个全局数组加上volatile,那么就不会抛这个异常出来(参考代码片段2)。

  另外一段代码就是模拟,工作线程copy 主内存的Integer,然后另一个线程不断给Integer赋值null,同样不会报错。这里有个技巧||和&&的区别。(参考代码片段1)


这里插上两段代码--

1.不会报错,因为max被copy到工作线程,当max加上volatile时,可以看到直接出错,因为max为null

public class Main {public static Integer max = null;public static void main(String[] args){Thread[] threads = new Thread[20];for (int i = 0; i < threads.length; i++) {            threads[i] = new Thread(new Runnable() {                @Override                public void run() {                                        while (true) {                    if(max == null || max > 10){                    max = 10;                    }                    }                }            });            threads[i].start();        }Thread t1 = new Thread(){public void run(){while(true){max = null;}}};t1.start();}}

2,这段代码会报错,因为重排序问题,导致l变量半初始化,ArrayList中的elementData并未成功初始化,调用l.size()时,发生NP错误。如果我们加上volatile修饰l,那么便不会报错了
</pre><span style="background-color: rgb(0, 0, 0);"></span><pre name="code" class="java">public class Test {public static List<String> l = new ArrayList<String>(1024); public static void main(String[] args) {Thread[] threads = new Thread[20];for (int i = 0; i < threads.length; i++) {            threads[i] = new Thread(new Runnable() {                @Override                public void run() {                    int j = 0;                    while (true) {                    synchronized (HmacSHA1.class) {                    if(l.size() < 1024)        l.add("ttt");}                    }                }            });            threads[i].start();        }Thread t1 = new Thread(){public void run(){while(true){List<String> local = l;l = new ArrayList<String>(1024);}}};t1.start();}}



0 0