一、概念
JMM与java并发编程相关:
1、抽象了线程与主内存的关系,例如线程的共享变量需要放到内存中进行读取
2、规定了java源代码到CPU可执行指令这个转换过程中需要遵守的规范,例如防止指令重排序造成的并发问题
二、并发编程的三个特性
1、原子性
一次操作或者多次操作,要么所有的操作全部都得到执行,要么都不执行。
在 Java 中,可以借助synchronized、各种 Lock 实现原子性。
2、可见性
当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。
在 Java 中,可以借助synchronized、volatile 以及各种 Lock 实现可见性。
3、有序性
在 Java 中,volatile 关键字可以禁止指令进行重排序优化
三、volatile

堆和方法区是线程共享的,因此共享变量存在堆和方法区内,而堆内存中的共享变量被不同的线程操作时,会加载到自己的工作内存中,也就是CPU高速缓存。
由于CPU的运算效率比内存IO效率快很多,因此引入了高速缓存cache,但是带来了缓存一致性的问题。
1、可以保证共享变量的可见性:volatile 关键字修饰的变量的read、load、use操作和assign、store、write必须是连续的,即修改后必须立即同步回主存,使用时必须从主存刷新,因此这样就可以保证共享变量在多线程环境下是可见的。
2、可以通过在对volatile修饰的变量的操作前后加上了内存屏障指令防止指令的重排序:指令重排序是因为处理器为了提高执行效率对代码进行优化,
双重效验锁代码如下:
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
- 为
uniqueInstance分配内存空间 - 初始化
uniqueInstance - 将
uniqueInstance指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化
四、synchronized
1、悲观锁:每次在获取共享资源的时候都会上锁
2、重量级锁:依赖于操作系统的监视器锁来实现,操作系统要想阻塞或者唤醒一个线程都需要从用户态转到内核态,需要较长的时间
3、可修饰方法、静态方法、代码块
4、可保证可见性、原子性
修饰代码块会对括号里指定的对象/类加锁:
synchronized(object)表示进入同步代码前要获得 给定对象的锁。synchronized(类.class)表示进入同步代码前要获得 给定 Class 的锁
五、乐观锁与悲观锁
1、乐观锁:总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。
version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当某个线程对数据进行更新时,读取到的version值与当前数据库中的version相等才更新Compare And Swap(比较与交换):用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。
但是存在ABA问题:
如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,但是不能说明它的值没有被其他线程修改过。因此可以再加上版本号和时间戳进一步验证
八、ReentrantLock
1、实现了Lock接口,是可重入的悲观锁
2、比synchronized强大,具有等待可中断、轮询、可实现公平锁等功能
3、ReentrantLock 的底层就是由 AQS (抽象队列同步器)来实现的。
AQS 使用 int 成员变量 state 表示同步状态,通过内置的 线程等待队列 来完成获取资源线程的排队工作。

以 ReentrantLock 为例,state 初始值为 0,表示未锁定状态。A 线程 lock() 时,会调用 tryAcquire() 独占该锁并将 state+1 。此后,其他线程再 tryAcquire() 时就会失败,直到 A 线程 unlock() 到 state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。