临界区(
Critical Section)
一个程序运行多个线程本身是没有问题的
问题出在多个线程访问共享资源
多个线程读共享资源其实也没有问题
在多个线程对共享资源读写操作时发生指令交错,
就会出现问题
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区,其共享资源为临
界资源
竞态条件(
Race Condition )
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条
件
为了避免临界区的竞态条件发生,有多种手段可以达到目的:
阻塞式的解决方案:synchronized,Lock
非阻塞式的解决方案:原子变量
注意:
虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:
互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点
synchronized 同步块是 Java 提供的一种原子性内置锁
,Java 中的每个对象都可以把它当作
一个同步锁来使用,这些 Java 内置的使用者看不到的锁被称为内置锁,也叫作监视器锁。
加锁方式:
分类
1、方法
实例方法,被锁对象为类的的实例对象:
public synchronized void method() {}
静态方法,被锁对象为类对象:
public static synchronized void method() {}
2、代码块
实例对象,被锁对象为类的实例对象:
synchronized(this) {}
class
对象,被锁对象为类对象:
synchronized(Sync.class) {}
任意
对象Object,被锁对象为实例对象Object:
private static String lock = "";
synchronized(lock) {}
synchronized 实际是用对象锁保证了临界区内代码的原子性
synchronized底层原理
synchronized是JVM内置锁
,基于
Monitor
机制实现,依赖底层操作系统的互斥原语
Mutex
(互斥量),它是一个重量级锁,性能较低。当然,
JVM内置锁在1.5之后版本做了重大的
优化,
如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、自适应自旋(Adaptive Spinning)等技术来减少锁操
作的开销
,内置锁的并发性能已经基本与Lock持平;
Java虚拟机通过一个同步结构支持方法和方法中的指令序列的同步:
Monitor
。
同步方法是通过方法中的access_flags中设置
ACC_SYNCHRONIZED
标志来实现;同步代码
块是通过
monitorenter和monitorexit
来实现。
两个指令的执行是JVM通过调用操作系统的互斥
原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态
之间来回切换,对性能有较大影响
Monitor(管程/监视器)
Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”。
管程是指管理共享变
量以及对共享变量操作的过程,让它们支持并发。
在Java 1.5之前,Java语言提供的唯一并发语言
就是管程,Java 1.5之后提供的SDK并发包也是以管程为基础的。除了Java之外,C/C++、C#等
高级语言也都是支持管程的。synchronized关键字和wait()、notify()、notifyAll()这三个方法是
Java中实现管程技术的组成部分。
notify()和notifyAll()分别何时使用
满足以下三个条件时,可以使用notify(),其余情况尽量使用notifyAll():
1. 所有等待线程拥有相同的等待条件;
2. 所有等待线程被唤醒后,执行相同的操作;
3. 只需要唤醒一个线程