java synchronized详解

背景

在多线程环境下同时访问共享资源会出现一些数据问题,此关键字就是用来保证线程安全的解决这一问题。

内存可见的问题

在了解synchronized之前先了解一下java内存模型,如下图:
image.png

  1. 线程1去主内存获取x的值读入本地内存此时x的值为1,进行运算x+1此时线程1的x值为2,然后写入主内存;
  2. 此时在线程1先入主内存之前,此时线程2去主内存读取了x的值,它读取到的值是1
  3. 最后x的值在主内存里的值是2,线程2读取到的是1,出现了内存不可见的问题。

synchronized关键字的使用方式

修饰方法

public class SynchronizedMethodExample {private int counter = 0;// synchronized 修饰的方法public synchronized void increment() {// 这里的操作是原子的,同一时刻只有一个线程能够执行counter++;System.out.println("Incremented counter to: " + counter);}public static void main(String[] args) {SynchronizedMethodExample example = new SynchronizedMethodExample();// 创建多个线程,同时调用 increment 方法Thread thread1 = new Thread(() -> {for (int i = 0; i < 5; i++) {example.increment();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 5; i++) {example.increment();}});// 启动线程thread1.start();thread2.start();try {// 等待两个线程执行完毕thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}// 打印最终的计数器值System.out.println("Final counter value: " + example.counter);}
}

由于 synchronized 修饰了 increment 方法,保证了同一时刻只有一个线程能够执行该方法。因此,两个线程在执行 increment 方法时会互斥,不会同时对 counter 进行操作。
执行结果

Incremented counter to: 1
Incremented counter to: 2
Incremented counter to: 3
Incremented counter to: 4
Incremented counter to: 5
Incremented counter to: 6
Incremented counter to: 7
Incremented counter to: 8
Incremented counter to: 9
Incremented counter to: 10
Final counter value: 10

修饰同步代码块

public class SynchronizedBlockExample {private int counter = 0;private final Object lockObject = new Object();  // 用于同步的对象public void increment() {// 一些非同步的代码synchronized (lockObject) {// 需要同步的代码块counter++;System.out.println("Incremented counter to: " + counter);}// 继续执行非同步的代码}public static void main(String[] args) {SynchronizedBlockExample example = new SynchronizedBlockExample();// 创建多个线程,同时调用 increment 方法Thread thread1 = new Thread(() -> {for (int i = 0; i < 5; i++) {example.increment();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 5; i++) {example.increment();}});// 启动线程thread1.start();thread2.start();try {// 等待两个线程执行完毕thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}// 打印最终的计数器值System.out.println("Final counter value: " + example.counter);}
}

在这个例子中,increment 方法包含了一个同步的代码块,使用 synchronized (lockObject)counter 进行递增操作。由于使用了 lockObject 作为同步对象,保证了两个线程在执行同步代码块时是互斥的,不会同时对 counter 进行操作。
执行结果:

Incremented counter to: 1
Incremented counter to: 2
Incremented counter to: 3
Incremented counter to: 4
Incremented counter to: 5
Incremented counter to: 6
Incremented counter to: 7
Incremented counter to: 8
Incremented counter to: 9
Incremented counter to: 10
Final counter value: 10

修饰静态方法

public class SynchronizedStaticMethodExample {private static int counter = 0;// synchronized 修饰的静态方法public static synchronized void increment() {// 这里的操作是原子的,同一时刻只有一个线程能够执行counter++;System.out.println("Incremented counter to: " + counter);}public static void main(String[] args) {// 创建多个线程,同时调用静态方法 incrementThread thread1 = new Thread(() -> {for (int i = 0; i < 5; i++) {SynchronizedStaticMethodExample.increment();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 5; i++) {SynchronizedStaticMethodExample.increment();}});// 启动线程thread1.start();thread2.start();try {// 等待两个线程执行完毕thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}// 打印最终的计数器值System.out.println("Final counter value: " + SynchronizedStaticMethodExample.counter);}
}

在这个例子中,increment 方法是一个静态方法,使用 synchronized 修饰。由于是静态方法,它锁定的是整个类的 Class 对象。两个线程无法同时调用 increment 方法,确保了对 counter 的递增操作是线程安全的。

Incremented counter to: 1
Incremented counter to: 2
Incremented counter to: 3
Incremented counter to: 4
Incremented counter to: 5
Incremented counter to: 6
Incremented counter to: 7
Incremented counter to: 8
Incremented counter to: 9
Incremented counter to: 10
Final counter value: 10

synchronized原理

java对象的组成

在讲原理之前我们先了解一下java对象的组成:
image.png
实例数据

  • 存放类的属性数据信息,包括父类的属性信息。

对齐填充

  • 由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。

对象头

  • 标志位(Flags):
    • Mark Word 的一些位用于存储对象的状态标志,例如是否被锁定、是否是偏向锁、是否是轻量级锁、是否是GC标记等。这些标志位的组合形成对象的状态信息。
  • 锁信息:
    • 存储锁相关的信息,用于实现对象的同步机制。
    • 对象可以处于无锁状态、偏向锁状态、轻量级锁状态或重量级锁状态。
  • 哈希码:
    • 用于支持对象的哈希操作,例如在哈希表中查找对象。
    • 哈希码是对象的标识,有助于提高哈希表的性能。
  • 对象分代年龄:
    • 用于支持分代垃圾回收算法。
    • 标识对象被创建后经历的垃圾回收次数。
  • 其他:
    • 可能还包含其他与垃圾回收、锁定等相关的信息。

ObjectMonitor

HotSpot虚拟机源码中ObjectMonitor.hpp。

ObjectMonitor::ObjectMonitor() {_header       = NULL;         // 监视器头部,用于保存状态信息_count        = 0;            // 计数器,用于记录监视器的使用次数_waiters      = 0;            // 等待线程数_recursions   = 0;            // 当前线程对该锁的递归次数_object       = NULL;         // 监视的对象_owner        = NULL;         // 拥有锁的线程_WaitSet      = NULL;         // 等待队列,存储等待该锁的线程_WaitSetLock  = 0 ;           // 用于保护等待队列的锁_Responsible  = NULL ;        // 释放锁的线程_succ         = NULL ;        // 后继监视器_cxq          = NULL ;        // 入口等待队列FreeNext      = NULL ;        // 空闲监视器链表的下一个_EntryList    = NULL ;        // 入口列表_SpinFreq     = 0 ;           // 自旋频率_SpinClock    = 0 ;           // 自旋时钟OwnerIsThread = 0 ;           // 拥有者是否为线程
}

ObjectMonitor.hpp 是 HotSpot 虚拟机(OpenJDK 的默认虚拟机实现)中用于实现对象监视器的头文件。对象监视器在 Java 中由 synchronized 关键字提供支持,用于实现多线程之间的同步。以下是对 ObjectMonitor.hpp 的一些关键部分的简要解释:

  1. ObjectMonitor 结构体:
    ObjectMonitor 是一个结构体,表示对象监视器。它包含了维护监视器状态和控制线程访问的各种信息。主要字段包括:
  • header:用于保存监视器的状态信息,如锁的状态、等待队列等。
  • owner:指向当前拥有锁的线程。
  • wait_set:等待队列,用于存储等待该锁的线程。
  • 等等。
  1. ObjectMonitor 头部(header):
    ObjectMonitor 的头部包含了一系列标志位,用于表示锁的状态、等待队列的状态等。这些标志位在字节层面上被设置和检查,以进行对锁的操作。一些常见的标志位有:
  • INFLATED:表示锁已经被膨胀,即从轻量级锁升级为重量级锁。
  • CONTENTION:表示锁有竞争。
  • HELD_EXCLUSIVELY:表示锁被当前线程独占。
  1. 等待队列(WaitSet):
    ObjectMonitor 中包含一个等待队列,用于存储等待该锁的线程。线程在等待队列中等待时,它会进入等待状态,直到被唤醒。等待队列的管理涉及到线程的入队和出队等操作。
  2. 线程入队和出队:
    ObjectMonitor 定义了一些方法,用于线程的入队和出队操作。例如:
  • void enter(Handle h):线程尝试进入临界区。
  • void exit():线程退出临界区。
  • void wait(bool, jlong, jlong):线程进入等待状态。
  • void notify()void notifyAll():唤醒一个或所有等待线程。
  1. 锁的状态转换:
    ObjectMonitor 定义了一些方法来实现锁状态的转换,例如从无锁到轻量级锁、从轻量级锁到重量级锁等。这些状态的转换涉及到了底层的原子操作和 CAS(Compare and Swap)等机制。
  2. 适应性自旋锁:
    HotSpot 虚拟机中的 ObjectMonitor 还包括适应性自旋锁的机制,该机制用于在获取锁时进行自旋,以避免线程进入阻塞状态。适应性自旋锁的目标是根据程序运行时的历史信息来动态调整自旋次数,以提高性能。

image.png

  1. 当多个线程同时访问同步代码块时,首先会进入到EntryList中,然后通过CAS的方式尝试将Monitor中的owner字段设置为当前线程,同时count加1,若发现之前的owner的值就是指向当前线程的,recursions也需要加1。如果CAS尝试获取锁失败,则进入到EntryList中;

  2. 当获取锁的线程调用wait()方法,则会将owner设置为null,同时count减1,recursions减1,当前线程加入到WaitSet中,等待被唤醒;

  3. 当前线程执行完同步代码块时,则会释放锁,count减1,recursions减1。当recursions的值为0时,说明线程已经释放了锁.

synchronized作用于同步代码块的字节码指令

在Java中,synchronized 作用于同步代码块的字节码指令主要涉及到 monitorentermonitorexit 指令。这两个指令用于实现监视器(monitor)的进入和退出,即获取和释放锁。
以下是一个简单的Java同步代码块的例子:

public class SynchronizedExample {private static final Object lock = new Object();public void synchronizedMethod() {synchronized (lock) {// 同步代码块// ...}}
}

对应的字节码大致如下:

public synchronizedMethod()VL0LINENUMBER 7 L0GETSTATIC SynchronizedExample.lock : Ljava/lang/Object;DUPASTORE 1MONITORENTER   // monitorenter 指令,获取锁// 同步代码块的字节码指令// ...L1LINENUMBER 9 L1ALOAD 1MONITOREXIT    // monitorexit 指令,释放锁ATHROW

解释一下上述字节码:

  1. GETSTATIC SynchronizedExample.lock : Ljava/lang/Object;: 获取 lock 字段的值,即获取锁对象。
  2. DUP: 复制栈顶数值,用于后续的 ASTORE 1 操作。
  3. ASTORE 1: 将锁对象的引用存储到局部变量1。
  4. MONITORENTER: 进入监视器,即获取锁。如果锁已经被其他线程占用,当前线程将阻塞等待。
  5. 同步代码块的具体实现。
  6. ALOAD 1: 将之前存储的锁对象引用加载到栈顶。
  7. MONITOREXIT: 退出监视器,即释放锁。
  8. ATHROW: 抛出异常,确保在任何情况下都会释放锁。

synchronized作用于方法字节码指令

以下是一个简单的例子,演示了 synchronized 修饰方法的字节码指令:

public class SynchronizedMethodExample {private int counter = 0;public synchronized void synchronizedMethod() {counter++;}
}

对应的字节码可能类似于:

public class SynchronizedMethodExample {private int counter;public SynchronizedMethodExample() {counter = 0;}public synchronized void synchronizedMethod() {// 获取锁monitorentertry {// 同步代码块counter++;} finally {// 释放锁monitorexit}}
}

在这个例子中:

  1. monitorenter 指令:在进入同步代码块之前获取锁。
  2. monitorexit 指令:在同步代码块执行完毕后释放锁。

synchronized锁的优化

JDK1.5之前,synchronized是属于重量级锁,重量级需要依赖于底层操作系统的Mutex Lock实现,然后操作系统需要切换用户态和内核态,这种切换的消耗非常大,所以性能相对来说并不好。

  1. 偏向锁(Biased Locking):
    • 在程序刚启动时,对象的锁大多数情况下只被一个线程所持有。为了提高性能,引入了偏向锁机制。
    • 当一个线程获取了对象的锁后,会在对象头的 Mark Word 中记录这个线程的 ID,表示这个锁被偏向于该线程。之后,该线程再次进入同步块时,无需再竞争锁,直接获得。
    • 偏向锁的目标是降低无竞争情况下的锁操作的开销。
  2. 轻量级锁(Lightweight Locking):
    • 当多个线程争夺同一个锁时,偏向锁会升级为轻量级锁。
    • 轻量级锁使用 CAS 操作来避免传统的互斥量(Mutex)的开销。如果有多个线程竞争同一个锁,会使用 CAS 操作来尝试获取锁,而不是阻塞线程。
  3. 自旋锁和自适应自旋锁:
    • 在无法获取锁时,线程可能会进行一定次数的自旋等待。自旋是一种忙等待的策略,避免了线程的阻塞和唤醒带来的开销。
    • 自适应自旋锁会根据锁的持有时间和竞争情况来动态调整自旋的次数,以在不同的场景下取得更好的性能。
  4. 锁消除和锁粗化:
    • 锁消除是指在编译期间,对一些明显不会发生竞争的锁进行消除,从而减少锁的使用。
    • 锁粗化是指将多个连续的操作都加锁,从而减少加锁和解锁的次数。

总结

synchronized 是 Java 中用于实现线程同步的关键字,它提供了对代码块、方法以及静态方法的同步支持。以下是关于 synchronized 锁的总结:

  1. 对象锁:
  • synchronized 可以用于实现对对象的同步,确保同一时刻只有一个线程能够访问被同步的代码块或方法。
  • 对象锁的粒度可以是对象实例(实例方法)或类(静态方法)。
  1. 方法锁:
  • synchronized 可以直接修饰方法,使整个方法具有同步性。此时,锁对象是方法所属的对象实例。
  • 修饰实例方法时,锁对象是方法调用的实例;修饰静态方法时,锁对象是类的 Class 对象。
  1. 代码块锁:
  • synchronized 还可以用于修饰代码块,指定锁的粒度更加灵活。
  • 在代码块中,需要指定一个对象作为锁,多个线程只有在获取了相同的锁时才会争夺执行权。
  1. 锁的释放:
  • 当一个线程获得了对象锁后,其他线程必须等待该线程释放锁才能进入同步代码块或方法。
  • 如果线程执行的同步代码块出现异常,锁会被自动释放。
  1. 偏向锁、轻量级锁和重量级锁:
  • 为了减小锁的操作开销,Java 中引入了偏向锁、轻量级锁和重量级锁的概念。
  • 在无竞争的情况下,偏向锁能够提高性能;在低竞争的情况下,轻量级锁可以减小锁的争用开销。
  1. 可重入性:
  • synchronized 具有可重入性,即同一个线程可以多次获得同一把锁而不会出现死锁。
  1. 性能优化:
  • Java 虚拟机和编译器对 synchronized 进行了一系列优化,包括偏向锁、轻量级锁、自适应自旋等,以提高多线程程序的性能。

总体而言,synchronized 是一种简单而有效的同步机制,用于确保多线程程序中对共享资源的安全访问。然而,在一些高并发场景下,可能需要考虑使用更灵活的锁机制,如 java.util.concurrent 包中提供的锁。

参考

https://zhuanlan.zhihu.com/p/377423211
https://juejin.cn/post/6973571891915128846

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/196784.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3DMM模型

目录 BFMBFM_200901_MorphableModel.matexp_pca.bintopology_info.npyexp_info.npy BFM BFM_2009 01_MorphableModel.mat from scipy.io import loadmat original_BFM loadmat("01_MorphableModel.mat") # dict_keys: [__header__, __version__, __globals__, # …

视频剪辑转码:mp4批量转成wmv视频,高效转换格式

在视频编辑和处理的领域&#xff0c;转换格式是一项常见的任务。在某些编辑和发布工作中&#xff0c;可能需要使用WMV格式。提前将素材转换为WMV可以节省在编辑过程中的时间和精力。从MP4到WMV的批量转换&#xff0c;不仅能使视频素材在不同的平台和设备上得到更好的兼容性&…

LoadBalancer将服务暴露到外部实现负载均衡Openelb-layer2模式配置介绍

目录 一.openelb简介 二.主要介绍layer2模式 1.简介 2.原理 3.部署 &#xff08;1&#xff09;先在集群master上开启kube-proxy的strictARP &#xff08;2&#xff09;应用下载openelb.yaml&#xff08;需要修改镜像地址&#xff09; &#xff08;3&#xff09;编写yam…

密集书库是什么意思?图书馆密集书库的书可以借出吗

密集书库是一种用于存储大量书籍和资料的高密度储存设施。它通常包括一系列钢制书架和可移动的储存架&#xff0c;使得书籍可以被紧密地排列和存储&#xff0c;以最大程度地利用存储空间。同时&#xff0c;密集书库还有各种自动化系统&#xff0c;如自动化取书系统、气候控制系…

安卓apk抓包(apk抓不到包怎么办)

起因 手机&#xff08;模拟器&#xff09;有时候抓不到apk的包&#xff0c;需要借助Postern设置一个代理&#xff0c;把模拟器的流量代理到物理机的burp上。 解决方案 使用Postern代理&#xff0c;把apk的流量代理到burp。 Postern是一个用于代理和网络流量路由的工具&#xf…

Linux Namespace技术

对应到容器技术&#xff0c;为了隔离不同类型的资源&#xff0c;Linux 内核里面实现了以下几种不同类型的 namespace。 UTS&#xff0c;对应的宏为 CLONE_NEWUTS&#xff0c;表示不同的 namespace 可以配置不同的 hostname。User&#xff0c;对应的宏为 CLONE_NEWUSER&#xf…

骨传导耳机会影响听力么?盘点骨传导耳机的好处与坏处都有哪些?

先说结论&#xff0c;使用骨传导耳机是不会影响听力的&#xff01;并且由于骨传导耳机的特殊传声原理&#xff0c;相比于传统的入耳式耳机&#xff0c;骨传导耳机拥有更多的优点&#xff0c;下面带大家了解一下骨传导耳机的优点和缺点都有哪些。 一、骨传导耳机的优点是什么&a…

kubectl获取ConfigMap导出YAML时如何忽略某些字段

前言&#xff1a; 当我们在使用Kubernetes时&#xff0c;常常需要通过kubectl命令行工具来管理资源。有时我们也想将某个资源的配置导出为YAML文件&#xff0c;这样做有助于版本控制和资源的迁移。然而&#xff0c;默认情况下&#xff0c;使用kubectl get命令导出资源配置会包…

JVM:双亲委派(未完结)

类加载 定义 一个java文件从编写代码到最终运行&#xff0c;必须要经历编译和类加载的过程&#xff0c;如下图&#xff08;图源自b站视频up主“跟着Mic学架构”&#xff09;。 编译就是把.java文件变成.class文件。类加载就是把.class文件加载到JVM内存中&#xff0c;得到一…

电子取证--windows下的volatility分析与讲解

1.volatility的安装 提示&#xff1a;我用的是2.6版本&#xff08;windows&#xff09;&#xff0c;如果直接下载的出现问题&#xff0c;用迅雷就可以解决 下载地址&#xff1a;Volatility 2.volatility的使用 1.进入终端&#xff0c;查看镜像的系统信息&#xff1a; volati…

Huawei FusionSphere FusionCompte FusionManager

什么是FusionSphere FusionSphere 解决方案不独立发布软件&#xff0c;由各配套部件发布&#xff0c;请参 《FusionSphere_V100R005C10U1_版本配套表_01》。 目前我们主要讨论FusionManager和FusionCompute两个组件。 什么是FusionCompte FusionCompute是华为提供的虚拟化软…

初识动态规划算法(题目加解析)

文章目录 什么是动态规划正文力扣题第 N 个泰波那契数三步问题使用最小花费爬楼梯 总结 什么是动态规划 线性动态规划&#xff1a;是可以用一个dp表来存储内容&#xff0c;并且找到规律存储,按照规律存储。让第i个位置的值等于题目要求的答案 >dp表&#xff1a;dp表就是用一…

SpringBoot——嵌入式 Servlet容器

一、如何定制和修改Servlet容器的相关配置 前言&#xff1a; SpringBoot在Web环境下&#xff0c;默认使用的是Tomact作为嵌入式的Servlet容器&#xff1b; 【1】修改和server相关的配置&#xff08;ServerProperties实现了EmbeddedServletContainerCustomizer&#xff09;例如…

PoE技术详解

标准的五类网线有四对双绞线&#xff0c;IEEE 802.3af和IEEE 802.3at允许两种用法&#xff1a;通过空闲线对供电或者数据线对供电。IEEE 802.3bt允许通过空闲线对供电、通过数据线对供电或者空闲线对和数据线对一起供电&#xff0c;如图16.1所示。 图 16.1 PoE供电线对 当在一…

整数的立方和

系列文章目录 进阶的卡莎C++_睡觉觉觉得的博客-CSDN博客数1的个数_睡觉觉觉得的博客-CSDN博客双精度浮点数的输入输出_睡觉觉觉得的博客-CSDN博客足球联赛积分_睡觉觉觉得的博客-CSDN博客大减价(一级)_睡觉觉觉得的博客-CSDN博客小写字母的判断_睡觉觉觉得的博客-CSDN博客纸币(…

C++ 系列 第四篇 C++ 数据类型上篇—基本类型

系列文章 C 系列 前篇 为什么学习C 及学习计划-CSDN博客 C 系列 第一篇 开发环境搭建&#xff08;WSL 方向&#xff09;-CSDN博客 C 系列 第二篇 你真的了解C吗&#xff1f;本篇带你走进C的世界-CSDN博客 C 系列 第三篇 C程序的基本结构-CSDN博客 前言 面向对象编程(OOP)的…

Star 10.4k!推荐一款国产跨平台、轻量级的文本编辑器,内置代码对比功能

notepad 相信大家从学习这一行就开始用了&#xff0c;它是开发者/互联网行业的上班族使用率最高的一款轻量级文本编辑器。但是它只能在Windows上进行使用&#xff0c;而且正常来说是收费的&#xff08;虽然用的是pj的&#xff09;。 对于想在MacOS、Linux上想使用&#xff0c;…

不瞒各位,不安装软件也能操作Xmind文档

大家好&#xff0c;我是小悟 作为搞技术的一个人群&#xff0c;时不时就要接收产品经理发过来的思维脑图&#xff0c;而此类文档往往是以Xmind编写的&#xff0c;如果你的电脑里面没有安装Xmind的话&#xff0c;不好意思&#xff0c;是打不开这类后缀结尾的文档。 打不开的话…

处理器中的TrustZone之安全状态

在这个主题中&#xff0c;我们将讨论处理器内对TrustZone的支持。其他部分则涵盖了在内存系统中的支持&#xff0c;以及建立在处理器和内存系统支持基础上的软件情况。 3.1 安全状态 在Arm架构中&#xff0c;有两个安全状态&#xff1a;安全状态和非安全状态。这些安全状态映射…

改善你的登录页:登录设计的极致指南!

登录页面相当于产品的立面。无论是网站还是APP&#xff0c;用户打开后&#xff0c;首先映入眼帘的就是登录页面&#xff0c;用户在这里进行下一步的操作。如果登录页面的UI设计错误&#xff0c;界面视觉混乱&#xff0c;往往会在用户详细了解产品之前关闭并离开。希望大家通过这…