JMM(并发编程) 要点
JMM(并发编程) 要点
- 保证多线程之间操作共享变量的正确性。
- 为了屏蔽系统和硬件的差异,让一套代码在不同平台下能到达相同的访问结果。
内存划分
JMM规定了内存主要划分为主内存和工作内存两种。
- 主内存对应的是Java堆中的对象实例部分,对应的是硬件的物理内存。
- 工作内存对应的是栈中的部分区域,对应的是寄存器和高速缓存。
为了提升读写性能,每条线程拥有各自的工作内存,工作内存中的变量是主内存中的一份拷贝,线程对变量的读取和写入,不能直接去操作主内存中的变量。当一个线程修改了自己工作内存中变量,其他线程不可见。
可见性问题
CPU中运行的线程从主存中拷贝共享对象到它的CPU缓存,并对共享对象进行更改。但这个变更对运行在其他CPU中的线程不可见,因为这个更改还没有flush到主存中。
要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。
内存屏障/禁止重排序
- Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序,从而让程序按我们预想的流程去执行。
- 内存屏障所做的另外一件事是强制刷出各种CPU cache。因此,任何CPU上的线程都能读取到这些数据的最新版本。
volatile
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
- 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
锁
- 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
- 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
synchronized/volatile
- volatile是线程同步的轻量级实现,volatile性能比synchronized好。
- volatile只用于变量而synchronized可以修饰方法以及代码块。
- 多线程访问volatile不会发生阻塞,而synchronized可能会发生阻塞。
- volatile能保证数据的可见性,但不能保证数据的原子性。
- volatile主要用于解决变量在多个线程之间的可见性,而 synchronized解决的是多个线程之间访问资源的同步性。
AQS(AbstractQueuedSynchronizer)
- AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器。
- AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
- CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
AQS的实现主要在于维护一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。队列中的每个节点是对线程的一个封装,包含线程基本信息,状态,等待的资源类型等。