Java并发编程基础

Java 并发编程研究的是什么

解决三个问题
并发编程最先要解决的就是性能问题, 而引入多线程的概念带来了新的问题,即安全问题和活跃问题

1. 线程安全问题
2. 活跃问题

死锁、活锁、饥饿

3. 性能问题

线程安全

线程安全就是线程同步的意思,当一个线程对一个线程安全的方法或者语句进行访问的时候,其他的线程不能在对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问。

多个线程同时运行时,这些线程可能会同时运行一个方法,如果每次运行的结果和单个线程运行的结果时一样的,而且其他的变量的值也和预期一样,就是线程安全的。线程安全的问题都是由全局变量及静态变量引起的。

若每个线程中对全局变量、静态变量只有读操作,而没有写操作,一般来说,这个全局变量时线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能会影响线程安全。

并发编程中的三大特性(重要概念)

1. 原子性:

Java 内存模型保证了 read,load,usee,assign,store,write,lock,unlock 操作具有原子性,例如对一个 int 类型的变量执行 assign 赋值操作,这个操作就是原子性的。但 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据(long,double)的读写操作划分为两次 32 位的操作来进行,即 load,store,read 和 write 操作可以不具备原子性。

原子性是指该操作是不可再分的.不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作.
竞态条件是指通过一个可能失效的观测结果来决定下一步的动作,比如 A 拿到数据 n,将要执行++操作, 而 B 在 A 拿到数据后操作前, 做了一次 n=n+1, 那么在 A 对 n 完成+1 并写回 n 以后, B 对 n 的操作就失效了.
原子操作是一个步骤,也可以是多个步骤,但是其顺序是不可以被打乱的,也不可以被切割而只执行其中的一部分(不可中断性)
将操作视为一个整体,资源在该次操作中保持一致,这就是原子性的核心特征.
原子操作是不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(切换到另一个线程).

2. 可见性:

可见性: 可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立刻看到。

CPU 从主存中读取数据的效率相对不高,现在主流的计算机中都会设有几级缓存,每个线程读取共享变量时,都会将该变量加载进其对应 CPU 的高速缓存中,修改变量后,CPU 会立刻更新缓存,但并不一定会立即将其写回主存(写进主存的时间不可预期),此时其他线程(尤其时不再同一 CPU 上执行的线程)访问该变量时,从主存中读到的就是酒数据,而非第一个线程更新后的数据。

这点实在操作系统或者说时硬件层面的机制,所以很多应用开发人员经常会忽略。

可见性就是要让一个 CPU 核心对数据的修改,对其他 CPU 核心立即可见。
volatile 关键词修饰的变量是指 CPU 从缓存读取数据时,其他 CPU 可见,然后都会从内存中读取数据

3. 有序性:

有序性: 是指在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序时因为发生了指令重排序。在 java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
volatile 关键字通过添加内存屏障的方式来禁止指令重排序,即重排序时不能把后面的指令放到内存屏障之前。
也可以用 synchronized 来保证有序性, 它保证每个时刻只有一个线程可以执行同步代码,相当于是让线程顺序执行同步代码。