为充分理解Java中多线程同步的实现原理,必须先理解JMM。
一、Java内存模型
Java Memory Model 是为了处理并发过程中的可见性、原子性、有序性问题的。
在Java虚拟机中,堆是一个线程共享的内存区域。堆中主要存放对象的实例、静态对象、数组等。堆中存放着一些共享变量。
每条线程都会有一个属于自己的本地内存,本地内存不允许其他线程访问。本地内存中存储的是共享变量的副本。
线程A若要改变主内存中的一个变量,会先改变线程A本地内存中的值,然后再写入主内存。
二、线程通信和线程同步
为了让程序在多线程的情况下正确执行,我们必须关注线程通信和线程同步这两个问题
并发中的两个关键问题:
-
线程之间如何通信?
- 共享内存机制,通过共享一些公共状态,从而实现线程间信息交换。—— 隐示通信
- 消息传递机制,通过直接调用
wait()
、notify()
、notifyAll()
这些方法进行线程间通信。—— 显示通信
-
线程之间如何同步?
同步是指程序中用于控制不同线程之间操作发生的相对顺序
- 在共享内存的并发模型中,同步是显示做的。程序员必须在程序中显示的调用某个方法、或某个代码段来达到线程互斥,如:
Synchronized
让多个线程排队访问。 - 在消息传递的并发模型中,由于消息的发送必须在消息接收之前,所以同步是隐示的。
- 在共享内存的并发模型中,同步是显示做的。程序员必须在程序中显示的调用某个方法、或某个代码段来达到线程互斥,如:
三、Volatile原理
对于申明了 Volatile
的变量进行写操作时,JVM会向处理器发送一个带Lock前缀的指令,将变量的缓存值写回主内存,在多处理器的情况下,为保证各个处理器缓存一致,就会实现缓存一致性协议(当其他处理器嗅探到缓存对应的主内存中的值发生了改变,当前缓存的旧值会失效,然后从主内存中加载新值到线程本地内存中缓存起来)
volatile使修改后的值立刻可见,从而实现同步。
通过 Volatile
实现同步,可以做到原子性、可见性,但不能做到复合操作的原子性,如: i++
自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。在多线程的情况下,自增操作的三个子操作可能会分割开执行,导致程序结果出现错误。
四、Synchronized原理
通过反编译可以看到Synchronized的原理:
Synchronized
原理上是实现了一个锁的机制。当方法或代码块被Synchronized修饰后,执行被修饰方法前先要获得Monitor(监视器,可理解为锁),执行完之后释放Monitor。
执行流程如下:
当Monitor被其他线程占用时,当前线程会进入SynchronizedQueue中等待Monitor被释放。
通过 Synchronized
实现同步,本质是实现了一个可重入锁,可以做到互斥性、可见性。