【谈一谈】并发_Synchronized

2024-03-11 04:28

本文主要是介绍【谈一谈】并发_Synchronized,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Synchronized

在这里插入图片描述

又到周末了,最近的话(有点子小日子不好过,哈哈哈!~)但是,我还是报之以歌哈哈哈

本次写关于并发_Synchronized的优化以及底层实现原理

说说心里话~其实是非常的累,原因应该怎么说呢?我发现自己在如今的这家公司,我处于一种活多钱少以及关键现在给的或自己不想干,因为没有一点儿子的技术性

你可能会问:那就跳呗!特么现在技术还不够啊,哈哈哈,真的是无语坏了,还说鸡毛,哈哈哈,就是吐槽!~

好吧~进入正文

本文总纲

在这里插入图片描述

1.类锁和对象锁

在上篇的总纲中我们已经介绍,这里我们复习下概念(来自官方解释~哈哈哈!)

在Java多线程编程中,锁主要用于控制对共享资源的访问,以保证数据的一致性和完整性。

类锁和对象锁是两种不同粒度的锁。

  1. 对象锁
    • 在Java中,每个对象都有一个内置锁(也称为监视器锁),
    • 当一个线程试图访问某个对象的synchronized代码块或方法时,该线程必须先获得该对象的锁。
    • 同一时刻只能有一个线程持有对象锁,其他线程必须等待。
    • 换句话说(大白话),对象锁是针对具体对象实例的,用于保护对象实例的并发安全

例如:

public class MyClass {public synchronized void method() {// 同一时间只有一个线程可以执行此方法}
}

或者

public class MyClass {public void method() {synchronized (this) {// 同一时间只有一个线程可以执行此代码块}}
}
  1. 类锁:类锁也是通过 synchronized 关键字来实现的,但不是作用于对象实例上,而是作用在整个类的Class对象上。在Java中,每个类在JVM中只有一个Class对象,所以类锁也是全局的,是一种粗粒度的锁。

例如:

public class MyClass {private static synchronized void classMethod() {// 同一时间只有一个线程可以执行此静态方法,不论多少个对象实例}
}

或者

public class MyClass {private static final Object classLock = new Object();public void instanceMethod() {synchronized (MyClass.classLock) {// 同一时间只有一个线程可以通过任何对象实例进入此同步代码块}}
}

总的来说,对象锁用于控制单个对象实例的并发访问,而类锁则用于控制所有对象实例对该类的静态成员或代码块的并发访问。

2.SYNCHRONIZED的优化

这里说得优化:主要是JDK的官方所说的三个优化:(在JDK1.6,JDK团队对Synchronized做的大量优化,因为在JDK1.5时候,被ReentrantLock完虐,被迫优化了,哈哈哈)

  1. 锁消除
  2. 锁膨胀
  3. 锁升级

1.锁消除(Lock Elimination)

Synchronized修饰的代码中,如果不存在操作临界资源的情况,会触发锁消除,

啥意思呢?就是即便你写了Synchronized,也不会触发(~就是这么的豪横!,哈哈)

具体点解释

Java虚拟机(JVM)的一项优化技术,主要应用于并发编程场景。

  • 在某些情况下,JVM能够检测到某个同步块内的数据在该同步块的执行过程中没有发生竞争,
  • 也就是说,不存在多个线程同时访问这段代码和共享数据的情况。
  • 此时,JVM就可以安全地消除对该同步块的锁定,从而提高程序运行效率。

举个例子:

假设array数组的元素都是不同的字符串对象,并且这段代码中的同步块只对局部变量localString进行操作,没有改变任何共享状态或与其他线程交互,

那么JVM通过分析可以判断这个同步块实际上是不必要的,因此可以进行锁消除

public void doSomething(int index) {String localString = array[index];synchronized (localString) {// 对localString进行操作,但并未涉及任何共享状态或与其他线程的交互}
}

2.锁膨胀(Lock Coarsening)

如若在一个循环中,频繁的获取和释放资源,这样会带来很大的消耗!

为了避免和减少这种状况,锁膨胀就出现了,就是将锁的范围扩大,避免频繁的竞争和获取锁资源带来的不必要消耗

官方术语

锁膨胀:

  • 是Java虚拟机(JVM)进行并发优化的一种手段,
  • 与锁消除相反,它不是去除不必要的锁,而是合并多个细粒度的锁为一个粗粒度的锁,
  • 以减少锁竞争和上下文切换开销,提高并发性能。

多线程编程中,

  • 如果一段代码涉及到对多个独立对象的同步操作,可能会导致频繁的锁获取和释放操作,增加系统开销
  • 锁膨胀就是将这些原本独立的对象锁合并成一个更粗粒度的锁,比如使用同一个锁来保护一组相关对象的操作,使得在多线程环境下,可以减少锁的竞争次数,提升系统的并发性能。

例如

假设有一个场景,程序需要对两个独立对象A和B进行同步访问:

synchronized (objectA) {// 对objectA进行操作
}
synchronized (objectB) {// 对objectB进行操作
}

在高并发场景下,不同的线程可能交替对A、B对象进行加锁,造成锁竞争激烈。

通过锁膨胀优化,可以将上述代码改为(实际上还是之前的上面的代码,只是JDK在这被优化如下面这串代码):

我们清晰的可看到:锁的范围扩大了(所有对A和B对象的同步操作都由一个共享的锁来控制,从而减少了锁竞争的可能性。)

private static final Object sharedLock = new Object();synchronized (sharedLock) {synchronized (objectA) {// 对objectA进行操作}synchronized (objectB) {// 对objectB进行操作}
}

3.锁升级(本文的重点!)(后面会说)

我们先说下背景:(这是为了更快的掌握嘛!~别急,慢慢来,比较快!)

在Java中,synchronized关键字用于实现线程间的同步控制,它提供了内置的锁机制来确保数据的并发访问安全。随着JDK版本的发展,尤其是从Java 6开始,为了优化synchronized在不同场景下的性能表现,引入了锁升级的概念,即根据竞争情况动态地将锁从一种状态转换为另一种状态。

  • 这种锁升级机制可以更高效地利用CPU资源,在无竞争或竞争不激烈的情况下提供更好的性能,
  • 而在高并发竞争情况下则退化为传统的重量级锁保证线程安全。
  • 需要注意的是,具体的锁升级策略和细节可能因不同的Java虚拟机实现而略有差异。(但是都差不多)

在这里插入图片描述

synchronized锁的升级过程主要包括以下四个阶段

  1. 无锁状态(Unlocked)
  • 当对象没有被任何线程锁定时,对象头中的锁标志位是未锁定的状态。
  1. 偏向锁(Biased Locking)
  • 在只有一个线程进入同步代码块的情况下,JVM会把锁设置为偏向模式,将锁绑定到当前获得锁的线程上,这样后续该线程再次进入同步代码块时无需再进行同步操作,从而减少获取锁和释放锁带来的开销。
  1. 轻量级锁(Lightweight Locking)
  • 当有第二个线程尝试进入已经被第一个线程持有偏向锁的方法或代码块时,偏向锁会被撤销,并升级为轻量级锁。轻量级锁采用CAS(Compare and Swap)操作尝试获取锁,如果获取失败,则通过自旋等待一段时间尝试重新获取,如果经过一定次数的自旋仍然无法成功获取锁,说明存在较为激烈的锁竞争,此时锁会进一步升级。
  1. 重量级锁(Heavyweight Locking)
  • 当自旋获取轻量级锁失败后,锁会升级为重量级锁。重量级锁会导致线程阻塞并进入操作系统层面的线程调度,直至锁被释放。此时,其他竞争线程将进入阻塞队列等待,持有锁的线程执行完毕后唤醒等待队列中的下一个线程继续执行。

3.Synchronized的实现原理

synchronized关键字的实现原理涉及到几个方面:

  1. Java对象结构、
  2. 虚拟机内部的锁状态管理
  3. 以及操作系统级别的线程同步机制等多个层面。

Java中的synchronized关键字是用于实现线程同步的一种机制,其底层实现原理主要包括以下几个核心要点

  1. 监视器锁(Monitor)

    • synchronized的实现基于Java对象头中的监视器锁。每个Java对象都有一个关联的监视器锁,也称为Monitor
    • 当线程试图访问被synchronized修饰的方法或代码块时,会先尝试获取该对象的监视器锁。
  2. 对象头(Object Header)

    • 在HotSpot虚拟机中,对象在内存中的布局包括对象头、实例数据和对齐填充等部分。对象头中的Mark Word存储了对象自身的运行时数据,其中包括锁状态标志位,这些标志位记录了当前锁的状态(如无锁、偏向锁、轻量级锁、重量级锁等)。
  3. 锁升级过程

    • 从JDK 6开始引入了锁优化策略,即自旋锁、偏向锁、轻量级锁到重量级锁的升级过程。
      • 偏向锁:如果只有一个线程访问同步块,则将锁偏向给这个线程,后续无需再次获取锁,减少了CAS操作。
      • 轻量级锁:当有第二个线程尝试获取偏向锁时,偏向锁会撤销并升级为轻量级锁,通过CAS操作尝试快速获取锁,失败则进行自旋等待。
      • 重量级锁:轻量级锁自旋一定次数后仍无法获取,或者存在多线程竞争时,会升级为重量级锁,此时会阻塞其他线程,直到持有锁的线程释放锁。
  4. 字节码指令

    • 编译器在编译过程中,会对synchronized关键字修饰的方法或代码块生成monitorenter和monitorexit两个字节码指令,分别对应于锁的获取与释放。
  5. 操作系统互斥原语

    • 重量级锁的实现依赖于操作系统的Mutex互斥原语,例如Linux下的futex系统调用,来确保同一时刻只有一个线程能够获得锁。
  6. 内存可见性

    • synchronized除了保证同步外,还提供了内存可见性。当一个线程退出synchronized代码块时,会确保对共享变量的所有更新对其他线程立即可见。

4,Synchronized的锁升级

最重要的还是MarkWord(我们看一个Java的对象堆)先卖个关子

前面的概念的我们已经清楚了

解释下MarkWord

在Java虚拟机中,MarkWord是对象头(Object Header)的一部分,它是一个与对象自身紧密相关的数据结构。

对于64位JVMMarkWord通常占用8个字节,并且存储了关于Java对象的运行时元数据和状态信息,这些信息包括但不限于:

  1. 锁状态标志:用于表示当前对象的锁状态,例如无锁、偏向锁、轻量级锁或重量级锁等。

  2. 哈希码(HashCode:在没有进行同步锁定时,MarkWord可能存储对象的哈希码以优化散列操作。

  3. GC分代年龄:记录对象在垃圾回收过程中的年龄,帮助垃圾收集器确定对象是否应该被晋升到老年代或者被回收。

  4. 偏向线程ID:在偏向锁的情况下,会记录最后一次获得该锁的线程ID,使得该线程可以无需再次获取锁就能访问对象。

  5. 线程持有的锁信息:当对象处于同步块中时,这里会存储相关线程的同步信息。

通过设计灵活的Mark Word结构,JVM可以在不增加额外内存开销的情况下实现高效的对象同步以及垃圾回收机制。

MarkWord的内容会在不同状态下动态改变,这种设计有助于提高性能并适应多线程环境下的各种并发控制需求。

为了在Java中可看到对象头的Markword信息,我们导入如下依赖:

在这里插入图片描述

整个锁的升级状态:

在这里插入图片描述

重量级锁的底层ObjectMonitor(可以不看,了解就行)

ObjectMonitor是一种用于实现线程同步和调度的内部数据结构,它包含了多个字段来管理锁的状态以及等待队列。

  • 当一个线程试图进入synchronized修饰的方法或代码块时,JVM会在对象头中设置相应的锁标志,并尝试获取对应的ObjectMonitor
  • 如果获取失败,则线程将会被阻塞,并进入上述的等待逻辑。而当线程退出synchronized区域时,会释放所持有的ObjectMonitor,从而可能唤醒等待队列中的其他线程继续执行

我们需要进入源码去看了,百度搜索openJdk

如果上面的打不开,就看这个hg.openjdk.org

先查看这个属性:

Monitor.hpp (不想看直接看下面的)

主要包括以下内容:

 ObjectMonitor() {_header       = NULL;   //存储着我们Markworld_count        = 0;		//竞争锁的线程个数_waiters      = 0,		//wait的线程个数_recursions   = 0;		//标识当前Synchronized的锁重入的次数_object       = NULL;_owner        = NULL;  //持有锁的线程_WaitSet      = NULL;	//保存wait线程信息,双向链表_WaitSetLock  = 0 ;		_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;	//获取锁资源失败后,线程要放到当前的单项链表中 FreeNext      = NULL ;_EntryList    = NULL ;   //_cxq以及被唤醒的waitSet中的线程,在一定的机制下,会放到EntryList中_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;_previous_owner_tid = 0;}

查看的C++的举例

int ObjectMonitor::TryLock (Thread * Self) {for (;;) {void * own = _owner ;if (own != NULL) return 0 ;if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {// Either guarantee _recursions == 0 or set _recursions = 0.assert (_recursions == 0, "invariant") ;assert (_owner == Self, "invariant") ;// CONSIDER: set or assert that OwnerIsThread == 1return 1 ;}// The lock had been free momentarily, but we lost the race to the lock.// Interference -- the CAS failed.// We can either return -1 or retry.// Retry doesn't make as much sense because the lock was just acquired.if (true) return -1 ;}
}

举个Java的例子

在这个例子中,我们有一个共享资源sharedResource,并创建了两个线程t1t2

每个线程都试图进入synchronized代码块来操作这个共享资源。

为了更好地理解重量级锁(通过ObjectMonitor实现)的工作原理,我们可以通过一个简单的Java代码示例来进行说明:

  • t1线程首先获取到sharedResource的锁时,它会在ObjectMonitor中设置_owner为t1线程,并将_count置为1。
  • 此时t2线程尝试获取相同的锁,由于t1线程持有该锁,所以t2线程会被阻塞,并放入_EntryList等待队列中进行自旋尝试获取锁。
  • t1线程在执行完同步代码块后会释放锁,即ObjectMonitor中的_owner字段变为null,_count减为0,同时唤醒_EntryList中的等待线程(这里是t2线程)。
  • t2线程被唤醒后再次尝试获取锁,这次成功获取到sharedResource的锁,然后执行同步代码块。
public class HeavyLockExample {private static Object sharedResource = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (sharedResource) {System.out.println("Thread 1 acquired the lock");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 1 releasing the lock");}});Thread t2 = new Thread(() -> {synchronized (sharedResource) {System.out.println("Thread 2 acquired the lock");}});t1.start();t2.start();}
}

整个过程中,ObjectMonitor扮演了关键角色,负责管理和调度多个线程对同一锁的竞争与协作,确保了并发环境下的数据一致性。而这种基于操作系统互斥原语实现的锁被称为“重量级锁”,因为它涉及到线程上下文切换等昂贵的操作,在高并发场景下可能成为性能瓶颈。

在这里插入图片描述

这篇关于【谈一谈】并发_Synchronized的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/796631

相关文章

大型网站架构演化(四)——使用应用服务器集群改善网站的并发能力

使用集群是网站解决高并发、海量数据问题的常用手段。当一台服务器的处理能力、存储空间不足时,不要企图去更换更强大的服务器,对大型服务器而言,不管多么强大的服务器,都满足不了网站持续增长的业务需求。这种情况下,更恰当的做法是增加一台服务器分担原有服务器的访问及存储压力。 对网站架构而言,只要能通过增加一台服务器的方式改善负载压力,就可以以同样的方式持续增加服务器不断改善系统性能,从而实现系统

Java并发编程—阻塞队列源码分析

在前面几篇文章中,我们讨论了同步容器(Hashtable、Vector),也讨论了并发容器(ConcurrentHashMap、CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便。今天我们来讨论另外一类容器:阻塞队列。   在前面我们接触的队列都是非阻塞队列,比如PriorityQueue、LinkedList(LinkedList是双向链表,它实现了D

Linux网络编程之简单并发服务器

1.概念 与前面介绍的循环服务器不同,并发服务器对服务请求并发处理。而循环服务器只能够一个一个的处理客户端的请求,显然效率很低. 并发服务器通过建立多个子进程来实现对请求的并发处理,但是由于不清楚请求客户端的数目,因此很难确定子进程的数目。因此可以动态增加子进程与事先分配的子进程相结合的方法来实现并发服务器。 2. 算法流程 (1)TCP简单并发服务器:     服务器子进程1:

23.并发

目录 一、一些概念二、进程和线程2.1 概念2.2 多线程导致的问题2.3 使用spawn创建新线程2.4 线程与move闭包 三、消息传递3.1 概念3.2 创建通道3.3 示例3.4 其它测试 四、共享状态并发4.1 互斥器4.2 Mutex的API4.3 多线程共享Mutex1)在多线程之间共享值;2)多线程和多所有权3) 原子引用计数4)RefCell/Rc与 Mutex/Arc的相

linux下I/O模型并发的epoll多进程池协程实现

方法1 主要思路: 定义了一个EventData结构体,用于存储事件相关的数据,如文件描述符、epoll 文件描述符、协程 ID 等。EchoDeal函数用于处理请求消息,并生成响应消息。handlerClient函数是协程的执行函数,用于处理客户端连接。它通过循环读取数据、解析请求、执行业务处理、发送响应等步骤,实现了对客户端请求的处理。handler函数是主函数,用于创建监听套接字、初始化

abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized

1,abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized 都不可以,因为abstract申明的方法是要求子类去实现的,abstract只是告诉你有这样一个接口,你要去实现,至于你的具体实现可以是native和synchronized,也可以不是,抽象方法是不关心这些事的,所以写这两个是没有意义的。然后,static方法是不会被覆

什么是并发

并发是指在一段时间内同时做多个事情,当有个线程在运行时,如果只有一个CPU,这种情况下计算机操作系统会采用并发技术实现并发运行,具体做法是采用“时间片轮询算法”,在一个时间段的线程代码运行时,其它线程处于就绪状。这种方式我们称之为并发(Concurrent)。 串行(serial):一个CPU上,按顺序完成多个任务。 并行(parallelism):指的是任务数小于等于cpu核数,即任务真

如何防止高并发

服务器集群就是指将很多服务器集中起来一起进行同一种服务,在客户端看来就象是只有一个服务器 集群可以利用多个计算机进行并行计算从而获得很高的计算速度,也可以用多个计算机做备份,从而使得任何一个机器坏了整个系统还是能正常运行。一旦在服务器上安装并运行了群集服务,该服务器即可加入群集。群集化操作可以减少单点故障数量,并且实现了群集化资源的高可用性。 1   我是这样做的: 1.用sy

高并发下的淘客返利系统性能优化

高并发下的淘客返利系统性能优化 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨在高并发场景下如何优化淘客返利系统的性能。随着电商行业的快速发展,淘客返利系统面临着日益增长的用户访问和数据处理压力,优化系统性能成为提升用户体验和保证系统稳定性的关键。 一、性能优化的重要性 在淘客返利系统中,高并发带来的挑战主

synchronized、volatile、Lock详解

在Java并发编程过程中,我们难免会遇到synchronized、volatile和lock,其中lock是一个类,而其余两个则是Java关键字。以下记录了小博开发过程中对这三者的理解,不足之处请多指教。 关于线程与进程请参考博文 以操作系统的角度述说线程与进程 synchronized   synchronized是Java中的关键字,是一种同步锁。有以下几种用法: 1、修饰方