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 ...
随机推荐
- Oracle中nlssort()函数排序功能
转自:https://www.iteye.com/blog/libaxiaoyuan-2199851 Oracle9i之前,中文是按照二进制编码进行排序的.在oracle9i中新增了按照拼音.部首.笔 ...
- python检测远程udp端口是否打开的代码
研发过程,把开发过程较好的代码收藏起来,如下的代码内容是关于python检测远程udp端口是否打开的代码,希望对各朋友有较大帮助. import socketimport threadingimpor ...
- django中使用AJAX时如何获取表单参数(按钮携带参数)
前提是函数和相应的视图路由都已经配置好了,然后就是表单了: <form id="SmsForm" method="post" class="a& ...
- HTML&CSS基础-xHtml语法规范
HTML&CSS基础-xHtml语法规范 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.html源码 <!DOCTYPE html> <html> ...
- 调用百度API返回经纬度
后台调用百度API接口生成: import java.io.BufferedReader; import java.io.IOException;import java.io.InputStreamR ...
- PHP编程实现阳历转换为阴历的方法
php类: 2 /** 3 *PHP编程实现阳历转换为阴历的方法 4 *根据实际情况所需进行调用 5 * 6 / 7 10 <?php class Lunar { public $MIN_YEA ...
- 事务@Transactional
在service类前加上@Transactional,声明这个service所有方法需要事务管理.每一个业务方法开始时都会打开一个事务. Spring默认情况下会对运行期例外(RunTimeExcep ...
- xpath+多进程爬取八零电子书百合之恋分类下所有小说。
代码 # 需要的库 import requests from lxml import etree from multiprocessing import Pool import os # 请求头 he ...
- Xenia and Weights(Codeforces Round #197 (Div. 2)+DP)
题目链接 传送门 思路 \(dp[i][j][k]\)表示第\(i\)次操作放\(j\)后与另一堆的重量差为\(k\)是否存在. 代码实现如下 #include <set> #includ ...
- 动态生成16位不重复随机数、随机创建2位ID
/** 1. * 动态生成16位不重复随机数 * * @return */ public synchronized static String generate16() { StringBuffer ...