Hello World

可见性、原子性和有序性

1. 并发问题

1.1 缓存导致的可见性问题

  一个线程对共享变量的修改,另一个线程可以立即看到,这称之为可见性。
  Java内存模型规定所有的变量存储在主内存中。每个线程都有自己的工作内存,线程在工作内存中保存了使用到的主内存中变量的副本拷贝,线程对变量的操作必须在工作内存中进行,不能直接读写主内存中的变量。不同线程之间无法访问对方工作内存的变量。线程之间共享变量值的传递均需要通过主内存来完成。
  当线程1对共享变量A进行修改之后,线程2的工作内存中A可能还不是最新的值。这时候线程1的操作对线程2就不具有可见性。

1.2 线程切换带来的原子性问题

  我们把一个或者多个操作在CPU执行期间不被打断的特性成为原子性。
  Java中的一条语句,在翻译为机器码之后,可能对应的是多个指令。
  比如:i++这个操作至少需要3条指令
把 i 的值从内存=加载到寄存器;执行+1操作;把值写入内存;
  假如 i=0,两个线程同时执行该操作,可能线程1执行完第一步,就切换到线程2执行,本来两个线程各执行一次后 i 的值应该为 2 ,此时就出现 两次递增操作后值为 1 的现象;

1.3 编译优化带来的有序性问题

  Java程序中,如果在本线程中观察,所有的操作都是有序的;如果在另一个线程观察,所有的操作都是无序的。前半句指的是线程内表现为串行的语义,后半句指的是指令重排序和主内存和工作内存同步延迟的问题。
  为了充分利用处理器的性能,处理器会对输入的代码进行乱序执行。在计算之后将乱序执行的结果重组,并保证该结果和顺序执行的结果一致,但是并不保证程序中各个语句的计算顺序和输入代码的顺序一致。Java虚拟机也有类似的指令重排序优化。
  比如:Object obj = new Object(),
  这条语句对应的指令为:
分配一块内存M;
在M上初始化 Object 对象;
将M的地址赋值给 obj;
  计算机经过优化后可能先执行第三步,再第二步,如果执行完第三步后切换到别的线程,若此时访问该变量则会发生空指针异常;

2. 内存模型

  Java引入内存模型,定义了一些规则约束了编译器和处理器的优化操作,主要针对可见性和有序性。
  锁:synchronzied和reentrantlock,只有获取到锁的线程才可以执行操作,其他线程等待,释放锁时相关变量强制刷新缓存
  volatile: 保证可见性,该关键字修饰的变量被修改后,其他工作内存的缓存的该变量失效,必须重新读取
  happen-before原则:先行操作的结果对后面操作是可见的

2.1 happen-before原则

happen-before即先行发生,A happen-before B,即A操作发生在B操作之前,A的操作结果对B是可见的
1. 程序次序原则:同一个线程中,前面的操作happen-before后面的操作
2. 锁定原则:锁释放前的操作happen-before后面的加锁后操作
3. volatile变量原则:volatile变量的写操作happen-before它的读操作
4. 线程启动原则:主线程中启动线程A,A启动之前的操作happen-before线程A中的操作
5. 线程中断原则:线程的中断操作happen-before线程检测到中断
6. 线程终止原则:线程中的所有操作happen-before线程终止
7. 对象终结原则:
posted @ 2019-03-04 12:52  小小忧愁米粒大  阅读(1083)  评论(0编辑  收藏  举报
瞅啥瞅,好好看书