Java并发编程

并发问题产生的三大根源

可见性问题:缓存导致的可见性问题

当多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

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

一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行。

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

程序执行的顺序按照代码的先后顺序执行。对于单线程,在执行代码时jvm会进行指令重排序,处理器为了提高效率,可以对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证保存最终执行结果和代码顺序执行的结果是一致的。

Java 内存模型

Java 内存模型规范了 JVM 如何提供按需禁用缓存和编译优化的方法。具体来说,这些方法包括 volatile、synchronized 和 final 三个关键字,以及六项 Happens-Before 规则。

synchronized锁

锁操作是具备happens-before关系的,解锁操作happens-before之后对同一把锁的加锁操作。实际上,在解锁的时候,JVM需要强制刷新缓存,使得当前线程所修改的内存对其他线程可见。

volatile

最原始的意义就是禁用 CPU 缓存。告诉编译器,对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入。volatile字段可以看成是一种不保证原子性的同步但保证可见性的特性,其性能往往是优于锁操作的。

final

final修饰的实例字段则是涉及到新建对象的发布问题。当一个对象包含final修饰的实例字段时,其他线程能够看到已经初始化的final实例字段,这是安全的。

Happens-Before 规则

Happens-Before表达的是:前面一个操作的结果对后续操作是可见的。Happens-Before 约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守 Happens-Before 规则。特性:传递性

  1. 程序的顺序性规则

    一个线程执行过程中,前面对某个变量的修改一定是对后续操作可见的

  2. volatile变量规则

    对一个volatile变量的写操作相对于后续对这个volatile变量的读操作可见

  3. 传递性

    A Happens-Before B B Happens-Before C 那么A Happens-Before C

  4. 管程中锁的规则

    线程A获得锁之后,对共享变量的操作对后来再获得锁的其他线程来说是可见的

  5. 线程 start() 规则

    它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。

  6. 线程 join() 规则

    主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现),当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。

  7. 线程中断规则

    对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。

  8. 对象终结规则

    一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。