并发编程基础二
Java 内存模型
计算机硬件模型:

由于线程工作空间缓存的存在,主存数据在多线程环境下会出现读写不一致情况。
监视器锁 synchronized
锁对象分为两种:
实例对象和 Class 对象。
不同锁对象之间的代码执行互不干扰,同一个类中加锁方法与不加锁方法执行互不干扰。
使用 synchronized 两种方式:
修饰普通方法,锁当前实例对象。修饰静态方法,锁当前类的 Class 对象。
修饰代码块,锁括号中的对象(实例对象或 Class 对象)。
synchronized特性
原子性
一系列操作要么全部执行要不全部不执行 -- 原子性。
被 synchronized 修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。因此,在 Java 中可以使用 synchronized 来保证方法和代码块内的操作是原子性的。
可见性
对一个变量解锁之前,必须先把此变量同步回主存中。这样解锁后,后续线程就可以访问到被修改后的值。
有序性
synchronized 本身是无法禁止指令重排和处理器优化的。
as-if-serial 语义:不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变。
编译器和处理器无论如何优化,都必须遵守 as-if-serial 语义。
synchronized 修饰的代码,同一时间只能被同一线程执行。所以,可以保证其有序性。
synchronized锁原理
JVM 基于进入和推出Monitor对象(每个线程私有一个可用monitor record列表)来实现方法和同步代码块,但两者的实现细节不同。
同步代码块:使用monitorenter 和 monitorexit 指令实现。
synchronize修饰的方法:取代之的是 ACC_SYNCHRONIZED标识,该标志指明了该方法是一个同步方法,从而执行相应的同步调用。
对于不同的对象头它们的总结如下表:
| 长度 | 内容 | 说明 |
|---|---|---|
| 32/64bit | MarkWord | 存储对象的hashCode,GC分代和锁信息 |
| 32/64bit | Klass Point | 存储到类元数据的指针 |
| 32/64bit | Array Length | 这个只针对数组对象而言,存储数组的长度 |
Java对象头中的MarkWord里面的存储对象如下表:
| 锁状态 | 25bit | 4bit | 1bit是否偏向锁 | 2bit锁标志 |
|---|---|---|---|---|
| 无锁状态 | 对象的hashCode | 对象分代年龄 | 0 | 01 |
在运行期间,Mark Word里存储的数据会随着锁标志的变化而变化,它的变化如下表所示:

synchronized锁优化(JDK6)
锁优化:无锁->偏向锁(Mark Word简单判断是否为当前线程再次请求获取锁)->
轻量级锁(采用CAS自旋获取锁)->重量级锁(会发生上下文切换,性能低)
锁膨胀不可逆
解决内存可见性 volatile
使用锁太笨重,对于解决内存可见性问题,Java 提供了弱形式的同步-- volatile 。该关键字确保对一个变量的更新对其他线程是可见的,线程中数据写入与读取是直接操作主内存而不是使用线程的缓存(工作空间)。
volatile 不是原子性的,仅保证了数据的可见性。当修改的新值依赖旧值时,获取 - 计算 -写入 ,三步操作不是原子的。
Java 指令的重排列
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排列。单线程下结果一致,多线程下存在问题。
使用 volatile 修饰变量 可以避免重排列。
其他
了解 Java 的 CAS 操作 与 Unsafe 类 比较并交换
了解 伪分享 概念--多个变量被存在同一个缓存行,多线程下读取与修改不同的资源会不断的刷新缓存,影响性能。(@sun.misc.Contended )
锁的概述:乐观与悲观、共享与独占、公平与非公平、可重入、自旋。