一个关于Java 多线程问题的知识点

这个程序运行结果会是什么?

public class Main {static class ListAdd {
        private static List list = new ArrayList();

        public void add() {
            list.add("baoer");
        }

        public int size() {
            return list.size();
        }

    }

    public static void main(String[] args) throws IOException {

        final ListAdd list = new ListAdd();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i=0 ; i < 10; i++) {
                        Thread.sleep(500);
                        list.add();
                        System.out.println("线程:" + Thread.currentThread().getName() + " 添加了一个元素" );
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"T1").start();


       new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {

                        if (list.size()== 5) {
                            System.out.println("当前线程收到通知 " + Thread.currentThread().getName() + "  list.size=5线程停止");
                            throw new RuntimeException();
                        }

                }
            }
        }, "T2").start();

       // fun();

    }

如果知道ArrayList不是线程安全的也许答案就是线程T1运行结束,T2一直执行下去不会抛出异常而结束。事实结果也确是这样。但这样的执行结果背后却值得深思。

问题1:这是因为有可能T2线程某次读入缓存的size为4,但下一次读入缓存的数字是6,所以永远进入不了if.

    但由于线程T1每次add之后都sleep 500 毫秒所以这种可能不存在。

问题2:在T2 if(list.size() == 5) 之前将size放入一个Hashset发现Hashset中只有一个值 0 。这说明size根本没有从主内存中刷新到T2工作内存中,为什么主存中size值都更新了还不刷新到工作内存中呢?不是有缓存一致性吗?

    原因就是 T2中的size根本没有在T2的缓存中!这是编译器干的事! 编译器发现是一个while(true),并且要频繁使用size,就会把size放在寄存器中提高访问速度,缓存不保存size。所以即使有缓存一致性size永远无法更新。

问题3: 为什么list是volatile 就会得到正确结果?

    对于volatile变量 编译器不会把它放入寄存器中,在缓存中volatile 可以保证可见性,并且根据happens-before规则 volatile 的读取一定在写入之后。

问题4: 为什么在if判断前加 System.out.println(list.size()); 也会的到正确结果?

   通过查看System.out.println 源码发现执行输出语句时要加同步锁,

    JMM关于synchronized的两条规定:

    1. 线程解锁前,必须把共享变量的最新值刷新到主内存中
    2. 线程加锁时,先清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值。

    由于清空工作内存中的值所以寄存器中的值也失效了,虽然此时值没在工作内存中,但也寄存器也会刷新再从工作内存中读取。

问题5: 为什么在if判断前加Thread.sleep(0)或者Thread.yield();也会的到正确结果?

    这涉及到了线程的上下文切换,一但切换上下文工作内存中的就值就会失效,系统保存了线程的状态,下次切换回来时重新从内存中读值。

原文地址:https://www.cnblogs.com/mibloom/p/9475252.html