Java共享内存模型带来的线程安全问题
public class SyncDemo {private static int count = 0;public static void increment() {count++;}public static void decrement() {count--;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5000; i++) {increment();}}}, "t1");Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5000; i++) {decrement();}}}, "t2");t1.start();t2.start();t1.join();t2.join();System.out.println(count);}}
上面的代码,我们的想法是把count进行5000次的自增和5000次的自减,希望看到的效果最后count是为0。但实际运行发现count可能是正数或者负数或者0。是因为在 Java 中对静态变量的自增,自减并不是原子操作。
count++的JVM指令
getstatic #2 <d4/SyncDemo.count : I> //获取静态变量count的值iconst_1 // 将int常量1压入操作数栈iadd //自增
count—的JVM指令
getstatic #2 <d4/SyncDemo.count : I>////获取静态变量count的值iconst_1 // 将int常量1压入操作数栈isub //自减
可以看出无论count++还是count—都不是一个原子操作,分为了三步,这也是为什么count的最后结果不一定为0的原因。因为可能在iadd之前,线程被挂起了,然后去执行了count—。或者是在isub之前被挂起了,去执行iadd,所以就会出现最后的结果不一致的问题。
临界区
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区,其共享资源为临界资源。
例如上面的代码
private static int count = 0;//临界资源public static void increment() {//临界区count++;}public static void decrement() {//临界区count--;}
竞态条件
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。
为了避免临界区的竞态条件发生,有多种手段可以达到目的:
- 阻塞式的解决方案:synchronized,Lock
- 非阻塞式的解决方案:原子变量
synchronized
synchronized 同步块是 Java 提供的一种原子性内置锁,Java 中的每个对象都可以把它当作一个同步锁来使用,这些 Java 内置的使用者看不到的锁被称为内置锁,也叫作监视器锁
加锁方式

解决之前的共享问题
方式一
public synchronized static void increment() {count++;}public synchronized static void decrement() {count--;}
方式二
private static final Object lock = new Object();public static void increment() {synchronized (lock) {count++;}}public static void decrement() {synchronized (lock) {count--;}}
synchronized底层原理
- synchronized是JVM内置锁,基于Monitor机制实现,依赖底层操作系统的互斥原语Mutex(互斥量)。
- Java虚拟机通过一个同步结构支持方法和方法中的指令序列的同步:monitor。
- 同步代码块是通过monitorenter和monitorexit来实现。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响
- 下面就是increment方法的字节码,可以看出在increment方法是由monitorenter和monitorexit指令包括起来的。

Monitor(管程/监视器)
Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”。管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。
Java语言的内置管程synchronized
Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量
notify()和notifyAll()分别何时使用
满足以下三个条件时,可以使用notify(),其余情况尽量使用notifyAll():
- 所有等待线程拥有相同的等待条件;
- 所有等待线程被唤醒后,执行相同的操作;
- 只需要唤醒一个线程。
