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 ...
随机推荐
- 什么是SAP Intelligent Robitic Process Automation - iRPA
所谓智慧企业,一个特征就是具备将复杂但低附加值的重复流程通过自动化的方式完成的能力.通过自动化,从而将宝贵的人力资源投入到更高附加值的工作中去,比如提供产品和服务的品质,提升用户体验.SAPGUI时代 ...
- Nginx 常用命令并实现最基本的反向代理
nginx 命令 测试配置文件格式是否正确:$ nginx -t 启动:nginx 重启:nginx -s reload 获取nginx进程号: ps -ef|grep nginx 停止进程(mast ...
- linux技能点三 find grep
find: 1. 按文件名查找 find . -name "a*.txt" 注意双引号: 2. 按文件大小查找 find .-size [+/-] ...
- python+BeautifulSoup+多进程爬取糗事百科图片
用到的库: import requests import os from bs4 import BeautifulSoup import time from multiprocessing impor ...
- Celery(异步任务,定时任务,周期任务)
1.什么是Celery Celery是基于Python实现的模块,用于异步.定时.周期任务的. 组成结构: 1.用户任务 app 2.管道broker 用于存储任务 官方推荐 redis/rabbit ...
- git merge与git rebase区别(转载)
这是最近看的讲的比较通俗易懂的rebase与merge的区别了 https://www.jianshu.com/p/4079284dd970
- Windows 2008R2 定时备份PostgreSQL 11.6及还原操作
PostgreSQL 自动备份,并删除10天前的备份文件. 第一步,创建脚本,命名back.bat文件,可直接点击执行或者CMD执行此批处理命令. @ECHO OFF @setlocal enable ...
- React-Router常见API
React-Router是React项目中处理路由的库. 1. HashRouter 通过hashchange监听路由的变化,通过window.location.hash赋值触发监听的变化. 本质是一 ...
- tensorflow2.0 学习(一)
虽说是按<TensorFlow深度学习>这本书来学习的,但是总会碰到新的问题!记录下这些问题,有利于巩固知新. 之前学过一些tensorflow1.0的知识,到RNN这章节,后面没有再继续 ...
- chsh
修改shell进程