Java多线程编程二 并发编程基础(下)

并发编程基础二

Java 内存模型

计算机硬件模型:

jmm.jpg

由于线程工作空间缓存的存在,主存数据在多线程环境下会出现读写不一致情况。

监视器锁 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里存储的数据会随着锁标志的变化而变化,它的变化如下表所示:

concurrent22.png

synchronized锁优化(JDK6)

锁优化:无锁->偏向锁(Mark Word简单判断是否为当前线程再次请求获取锁)->

轻量级锁(采用CAS自旋获取锁)->重量级锁(会发生上下文切换,性能低)

锁膨胀不可逆

解决内存可见性 volatile

使用锁太笨重,对于解决内存可见性问题,Java 提供了弱形式的同步-- volatile 。该关键字确保对一个变量的更新对其他线程是可见的,线程中数据写入与读取是直接操作主内存而不是使用线程的缓存(工作空间)。

volatile 不是原子性的,仅保证了数据的可见性。当修改的新值依赖旧值时,获取 - 计算 -写入 ,三步操作不是原子的。

Java 指令的重排列

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排列。单线程下结果一致,多线程下存在问题。

使用 volatile 修饰变量 可以避免重排列。

其他

了解 Java 的 CAS 操作 与 Unsafe 类 比较并交换

了解 伪分享 概念--多个变量被存在同一个缓存行,多线程下读取与修改不同的资源会不断的刷新缓存,影响性能。(@sun.misc.Contended )

锁的概述:乐观与悲观共享与独占公平与非公平可重入自旋

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容