美文网首页
java内存模型与volatile

java内存模型与volatile

作者: 叫我C30混凝土 | 来源:发表于2020-10-18 22:44 被阅读0次
java-memory-model.png

Java内存模型文档
https://download.oracle.com/otndocs/jcp/memory_model-1.0-pfd-spec-oth-JSpec/
中文版:
http://ifeve.com/jsr133-cn/

共有变量的私有副本.png

由于目前多核CPU与自己的高速缓存交换速度要远远快于和主内存交换,因此如果每个线程都有一个私有副本(工作内存),JVM可以大大提高效率;但带来的后果就是,私有副本要定期和主内存同步,而看上去两个线程在修改同样的一个变量,但实际上由于工作内存的存在,他们其实都在修改自己的私有副本;

public class VolatileTest {
    private static boolean cancelled = false;
    //有可能存在两个线程共同读写一个副本,导致主线程中cancelled被修改为true时,另一个线程看不到,cancelled 依旧为false
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if (cancelled) {
                    //取消自己做的事
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //做一些定时器相关的工作
            }
        }

        ).start();

        Thread.sleep(100000);
        cancelled = true;
    }

}

规避该问题:

volatile的保证

  • 可见性,非原子性
    • 写入volatile变量会直接写入主内存
    • 从volatile变量读取会直接读取主内存
public class VolatileTest {
    private static volatile boolean cancelled = false; //这里加volatile 最合适
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if (cancelled) {
                    //取消自己做的事
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //做一些定时器相关的工作
            }
        }

        ).start();

        Thread.sleep(100000);
        cancelled = true;
    }

}
  • 禁止指令重排
    • 编译器和处理器都可能对指令进行重排,导致问题
private static boolean initializationFinished = false;

    public static void main(String[] args) throws InterruptedException {
        init();
        initializationFinished = true;
        // 这两个指令没有必要的联系,所以在JIT编译/cpu流水线执行过程中,可能真正执行的顺序与代码顺序相反;
    }

    private static void init(){
        //假装自己在初始化工作
    }
private static boolean initializationFinished = false;

    // 对于两个线程来说,由于指令重排存在,使得两个线程可能存在先修改initializationFinished = true;而并没有init()的情况;
    // 结果导致未初始化就进行后续操作;
    public static void main(String[] args) {
        init();
        initializationFinished = true;

        new Thread(() -> {
            while (true) {
                if (initializationFinished) {
                    doSomething();
                    break;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private static void doSomething() {
        //假装自己在初始化结束后,执行一些有意义的工作
    }

    private static void init() {
        //假装自己在初始化工作
    }
private static volatile boolean initializationFinished = false;

    public static void main(String[] args) {
        init();
        // 在读之前产生内存屏障
        initializationFinished = true;
        // 在写之后产生内存屏障
        // 阻止指令重排
        new Thread(() -> {
            while (true) {
                if (initializationFinished) {
                    doSomething();
                    break;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private static void doSomething() {
        //假装自己在初始化结束后,执行一些有意义的工作
    }

    private static void init() {
        //假装自己在初始化工作
    }
  • 有同步的时候无需volatile
    • synchronized/Lock/AtomicInteger
方法一
public class VolatileTest {
    private static boolean initializationFinished = false;

    public static void main(String[] args) {
        init();
        //此时不再需要volatile声明变量
        synchronized (VolatileTest.class){
            initializationFinished = true;
        }
        new Thread(() -> {
            while (true) {
                if (initializationFinished) {
                    doSomething();
                    break;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private static void doSomething() {
        //假装自己在初始化结束后,执行一些有意义的工作
    }

    private static void init() {
        //假装自己在初始化工作
    }

}

方法二
public class VolatileTest {
    private static boolean initializationFinished = false;

    static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        init();
        lock.lock();
        initializationFinished = true;
        lock.unlock();
        new Thread(() -> {
            while (true) {
                if (initializationFinished) {
                    doSomething();
                    break;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private static void doSomething() {
        //假装自己在初始化结束后,执行一些有意义的工作
    }

    private static void init() {
        //假装自己在初始化工作
    }

}

方法三
public class VolatileTest {
    private static AtomicBoolean initializationFinished = new AtomicBoolean(false);

    public static void main(String[] args) {
        init();
        initializationFinished.set(true);
        new Thread(() -> {
            while (true) {
                if (initializationFinished.get()) {
                    doSomething();
                    break;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private static void doSomething() {
        //假装自己在初始化结束后,执行一些有意义的工作
    }

    private static void init() {
        //假装自己在初始化工作
    }
}

附:面试-并发容器之ConcurrentHashMap
https://blog.csdn.net/a979331856/article/details/105922069

相关文章

网友评论

      本文标题:java内存模型与volatile

      本文链接:https://www.haomeiwen.com/subject/iwfqmktx.html