Java-内存模型 synchronized 的内存语义
synchronized 具有使每个线程依次排队操作共享变量的功能。这种同步机制效率很低,但 synchronized 是其它并发容器实现的基础。
一、锁对象及 synchronized 的使用
synchronized 通过互斥锁(Mutex Lock)来实现,同一时刻,只有获得锁的线程才可以执行锁内的代码。
锁对象分为两种:
实例对象(一个类有多个)和 Class 对象(一个类只有一个)。
不同锁对象之间的代码执行互不干扰,同一个类中加锁方法与不加锁方法执行互不干扰。
使用 synchronized 也有两种方式:
修饰普通方法,锁当前实例对象。修饰静态方法,锁当前类的 Class 对象。
修饰代码块,锁括号中的对象(实例对象或 Class 对象)。
class Xz {
// 类锁
public static synchronized void aa() {
for (int i = 0; i < 10; i++) {
System.out.println("aaa");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 对象锁
public synchronized void bb() {
for (int i = 0; i < 10; i++) {
System.out.println("bbb");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 无锁
public void cc() {
for (int i = 0; i < 10; i++) {
System.out.println("ccc");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SynchronizedTest {
public static void main(String[] args) {
Xz xz = new Xz();
// 执行互不干扰
new Thread(() -> {
Xz.aa();
}).start();
new Thread(() -> {
xz.bb();
}).start();
new Thread(() -> {
xz.cc();
}).start();
}
}
二、特性
原子性
被 synchronized 修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。因此,在 Java 中可以使用 synchronized 来保证方法和代码块内的操作是原子性的。
可见性
对一个变量解锁之前,必须先把此变量同步回主存中。这样解锁后,后续线程就可以访问到被修改后的值。
有序性
synchronized 本身是无法禁止指令重排和处理器优化的。
as-if-serial 语义:不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变。
编译器和处理器无论如何优化,都必须遵守 as-if-serial 语义。
synchronized 修饰的代码,同一时间只能被同一线程执行。所以,可以保证其有序性。
三、synchronized 的实现:monitor 和 ACC_SYNCHRONIZED
package com; /**
* 编译:javac com\SynchronizedTest.java
* 反编译:javap -v com\SynchronizedTest
*/
public class SynchronizedTest {
public static void main(String[] args) {
synchronized (SynchronizedTest.class) {
System.out.println("haha!");
}
} public synchronized void xx(){
System.out.println("xixi!");
}
}
反编译上述代码,结果如下(省去了不相关信息)
{
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // class com/SynchronizedTest
2: dup
3: astore_1
4: monitorenter // 获取锁,之后其它要执行该段代码的线程需要等锁释放
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String haha!
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit // 锁内代码执行完毕,释放锁,其他线程可再次获取锁
15: goto 23
18: astore_2
19: aload_1
20: monitorexit // 锁内代码发生异常时自动释放锁
21: aload_2
22: athrow
23: return
public synchronized void xx();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED // 线程在执行有 ACC_SYNCHRONIZED 标志的方法时需要先获得锁
Code:
stack=2, locals=1, args_size=1
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String xixi!
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
line 16: 8
}
同步代码块
JVM 规范描述:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.14
使用 monitorenter 和 monitorexit 两个指令实现。
每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0。
当一个线程获得锁(执行 monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增(可重入性)。当同一个线程释放锁(执行 monitorexit)后,该计数器自减。当计数器为0的时候,锁将被释放。
同步方法
JVM 规范描述:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.10
同步方法的常量池中会有一个 ACC_SYNCHRONIZED 标志。当线程访问时候,会检查是否有 ACC_SYNCHRONIZED,有则需要先获得锁,然后才能执行方法,执行完或执行发生异常都会自动释放锁。
ACC_SYNCHRONIZED 也是基于 Monitor 实现的。
四、Mark Word 与 ObjectMonitor
对象的实例保存在堆上,对象的元数据保存在方法区,对象的引用保存在栈上。
对象的实例在堆中的数据可分为对象头(包含 Mark Word 和 Class Metadata Address),实例数据,对齐填充(HotSpot 要求对象的起止地址必须是 8 的倍数)。
对象头在 JVM 中对应的对象文件为 markOop.hpp,其中引用了 ObjectMonitor 对象文件。
Mark Word
对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,对象头被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。
下图描述了在 32 位虚拟机上,非数组对象在不同状态时 mark word 各个比特位区间的含义。如果是数组对象的话,还会有一个额外的部分用于存储数组长度。


源码中(markOop.hpp)关于对象头对象的定义,主要包含了 GC 分代年龄、锁状态标记、哈希码、epoch(偏向时间戳)等信息。
enum { age_bits = ,
lock_bits = ,
biased_lock_bits = ,
max_hash_bits = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
hash_bits = max_hash_bits > ? : max_hash_bits,
cms_bits = LP64_ONLY() NOT_LP64(),
epoch_bits =
};
源码中(markOop.hpp)关于对象头中锁状态的定义。
enum { locked_value = , // 00 轻量级锁
unlocked_value = , // 001 无锁
monitor_value = , // 10 监视器锁,膨胀锁,重量级锁
marked_value = , // 11 GC标记
biased_lock_pattern = // 101 偏向锁
};
ObjectMonitor
源码中(objectMonitor.hpp)关于 Monitor 对象的定义。
ObjectMonitor() {
_header = NULL;
_count = ; // 用来记录该线程获取锁的次数
_waiters = ,
_recursions = ; // 锁的重入次数
_object = NULL;
_owner = NULL; // 指向持有 ObjectMonitor 对象的线程
_WaitSet = NULL; // 存放处于 wait 状态的线程队列
_WaitSetLock = ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 存放处于等待锁 block 状态的线程队列
_SpinFreq = ;
_SpinClock = ;
OwnerIsThread = ;
_previous_owner_tid = ;
}
当多个线程同时访问一段同步代码时,首先会进入 _EntryList 队列中,当某个线程获取到对象的 monitor 后进入 _Owner 区域并把 monitor 中的 _owner 变量设置为当前线程,同时 monitor 中的计数器 _count 加 1。即获得对象锁。
若持有 monitor 的线程调用 wait() 方法,将释放当前持有的 monitor,_owner 变量恢复为 null,_count 自减 1,同时该线程进入 _WaitSet 集合中等待被唤醒。
若当前线程执行完毕也将释放 monitor(锁) 并复位变量的值,以便其他线程进入获取 monitor(锁)。

https://www.hollischuang.com/archives/2637
https://blog.csdn.net/lengxiao1993/article/details/81568130
https://www.cnblogs.com/dennyzhangdd/p/6734638.html
https://juejin.im/post/5d5374076fb9a06ac76da894
Java-内存模型 synchronized 的内存语义的更多相关文章
- Java内存模型-volatile的内存语义
一 引言 听说在Java 5之前volatile关键字备受争议,所以本文也不讨论1.5版本之前的volatile.本文主要针对1.5后即JSR-133针对volatile做了强化后的了解. 二 vol ...
- 【java】java内存模型(2)--volatile内存语义详解
多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”.可见性的意思是当一个线程 ...
- Java内存模型、JVM内存结构和Java对象模型
JVM内存结构 我们都知道,Java代码是要运行在虚拟机上的,而虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途.其中有些区域随着虚拟机进程的启动而存 ...
- Java 内存模型和硬件内存架构笔记
前言 可跟<主存存取和磁盘存取原理笔记>串着看 https://blog.csdn.net/suifeng3051/article/details/52611310 杂技 Java 内存模 ...
- Java并发编程里的volatile。Java内存模型核CPU内存架构的对应关系
CPU内存架构:https://www.jianshu.com/p/3d1eb589b48e Java内存模型:https://www.jianshu.com/p/27a9003c33f4 多线程下的 ...
- JAVA内存模型与JVM内存结构
问题:什么事java内存模型? 首先呢不要答堆.栈.方法区.这是JVM的内存结构.下面阐述了JMM和JVM的区别和自己对JMM的见解 1.Java内存模型(JMM):即多线程相关的.定义了一个线程对另 ...
- 04-JVM内存模型:直接内存
1.1.什么是直接内存(Derect Memory) 在内存模型最开始的章节中,我们画出了JVM的内存模型,里面并不包含直接内存,也就是说这块内存区域并不是JVM运行时数据区的一部分,但它却会被频繁的 ...
- 内存模型 Memory model 内存分布及程序运行中(BSS段、数据段、代码段、堆栈
C语言中内存分布及程序运行中(BSS段.数据段.代码段.堆栈) - 秦宝艳的个人页面 - 开源中国 https://my.oschina.net/pollybl1255/blog/140323 Mem ...
- Redis内存模型(1):内存统计及划分
1. 内存统计 查看命令:info memory 示例: 部分含义: used_memory: Redis分配器分配的内存总量(单位是字节),包括使用的虚拟内存. used_memory_rss: R ...
随机推荐
- FI-FBV0 - No batch input data for screen SAPMF05A 0700
在预制凭证过账的时候报错:没有屏幕SAPMF05A 0700 的批输入数据 https://answers.sap.com/questions/7203025/fbv0-no-batch-input- ...
- Firebird 事务隔离级别
各种RDBMS事务隔离都差不多,Firebird 中大致分为3类: CONCURRENCY.READ_COMMITTED.CONSISTENCY. 在提供的数据库驱动里可设置的事务隔离级别大致如下3类 ...
- 深入理解JVM-java内存区域与内存溢出异常
1.内存模型概述 2.运行时数据区 2.1.程序计数器 理解: 1.什么是程序计数器 2.线程私有还是共享 引入难点: 理解什么是 native方法 简单地讲,一个Native Method就是一个j ...
- .net core jenkins持续集成
执行 Shell pwd ls echo ${PATH} whoami which dotnet dotnet --info dotnet --version echo '============== ...
- ffmpeg音频视频转格式工具使用
ffmpeg是音频视频编解码工具,是一个开源项目,可以改变视频格式,比如mp4格式转ogg格式(有格式工厂,多这个东西纯属自己娱乐一下) 官方网址:www.ffmpeg.org 下载后找到ffmpeg ...
- 21.centos7基础学习与积累-007-远程连接
从头开始积累centos7系统运用 大牛博客:https://blog.51cto.com/yangrong/p5 IP地址: 互联网上的计算机 都会有一个唯一的32位的地址,ip地址,我们访问服务器 ...
- 微信小程序中,如果没有参数,如何设置默认参数?
现在学会小程序,这方面的知识,需要积累. 现在的情况是这样: 如果想从后端获取产品列表,而这些列表是可以根据分类来获取的,也是可以获取所有产品的. 那么,为了不使小程序报错,那么,我们就可以将不传的参 ...
- Spring Cloud Zuul网关(快速搭建)
zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用. 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架.相当于是设备和 Netflix ...
- Python使用pip安装matplotlib模块
matplotlib是python中强大的画图模块. 首先确保已经安装python,然后用pip来安装matplotlib模块. 进入到cmd窗口下,建议执行python -m pip install ...
- Linux CentOS7.x安装docker全过程
1.在安装docker之前,首先使用yum -y remove docker命令移除系统中已有的旧版本的docker yum -y remove docker 这里显示该系统没有安装过docker: ...