读《Java并发》— Java内存模型

"上一章" 介绍了线程安全性,这一章称为"对象的共享",书中重点介绍了如何共享和发布对象,这两章是并发编程中非常基础却很重要的部分。在本章,首先介绍了什么是可见性问题,然后介绍了Java内存模型,讨论什么是内存可见性以及java保证内存可见性的方式,在此基础上介绍如何设计线程安全的对象,如何使用线程封闭技术和设计不可变对象来避免同步,最后再重点探讨如何安全地发布对象。由于内容较多,我将这一章分拆为几篇来阐述自己对本章的理解,这是第一篇。 Java的并发机制基于共享内存,要理解对象间的共享关系,则离不开对象间的内存关系,这涉及到本章要介绍的一个重要概念:Java内存模型,又称 JMM。 1. 内存可见性 上边提到,Java的并发机制是采用的是 共享内存模型,因此,在并发环境中保证对象间的内存可见性是并发编程解决的主要问题。 什么是内存可见性?可见性是一个复杂的问题,它表示程序中的变量在写入值后是否能够立即读取到。在单线程环境中,由于写入变量和读取变量都是在单线程中进行的,因此能够保证总能读取到修改后的值。但在多线程环境下,却无法保证,可能一个线程修改变量的值,而另外的线程并不能正确读取被修改的变量的值,除非我们使用变量同步等机制来保证可见性。 为什么多线程环境下变量就不能保证可见性了呢?稍后介绍JMM时再来讨论,先看一个示例。 内存可见性(Memory Visibility):某些线程修改了变量的值,正在读该变量的线程能够立即读取到修改后的值。 @NotThreadSafe public class UnsafeSequence { private int count; public void increment() { count++; } public int getCount() { return count; } } 上边的示例,count变量被多个线程共享,因此不能保证 getCount() 总能读取到 increment() 增加后的值。那是不是在 increment() 方法上使用 synchronized 进行同步就能保证可见性了呢?答案是不行。虽然使用同步能够保证只有一个线程修改count的值,但是其他多个线程仍然可能读到 失效的值,因此必须在 getCount() 上也使用同步,见 "这里"。 再看一个示例,如下边的代码: @NotThreadSafe public class NoVisibility { private static boolean ready; private static int anInt; private static class VariableReader implements Runnable { @Override public void run() { while (!ready) { Thread.yield(); } System.out.println(anInt); } } public static void main(String[] args) { new Thread(new VariableReader(), "read").start(); anInt = 47; (1) ready = true; (2) } } ...

2022-03-28 · 3 min · 578 words · Hank