• 03多线程-synchronized的理解


    一句话导读

            synchronized 是 Java 中用于实现线程同步的关键字,它可以应用在方法和代码块上,用于保证多个线程之间对共享资源的安全访问,其底层原理是通过对象的锁信息和监视器来实现线程的互斥访问和同步操作。

    目录

    一句话导读

    一、认识synchronized

    二、synchronized原理

    1.对象头

    2.监视器

    3.锁状态及锁升级


    一、认识synchronized

            synchronized 是 Java 中用于实现线程同步的关键字,它可以应用在方法和代码块上,用于保证多个线程之间对共享资源的安全访问。每个Java对象都隐含有一把锁,这里称为Java内置锁(或者对象锁、隐式锁)。使用synchronized(syncObject)调用相当于获取syncObject的内置锁,所以可以使用内置锁对临界区代码段进行排他性保护。

            synchronized有三种使用方法:同步方法、同步代码块、静态同步方法

    二、synchronized原理

            理解synchronized之前,我们先了解下Java对象的知识,在内存中,Java对象分为三块区域:对象头、实例数据和对齐填充

            对象头:hotspot虚拟机对象的对象头部分包括两类信息,一是用于存储对象的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据称作Mark Word

            实例数据:是对象真正存储的有效信息,也就是我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的还是在子类中定义的字段都不行记录下来。

            对齐填充:由于hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全

            在Java中,synchronized关键字的底层原理涉及到对象头(Object Header)和监视器(Monitor)的概念

    1.对象头

            每个Java对象都有一个对象头,对象头包含一些用于对象管理的元数据信息,其中之一是用于实现同步的锁信息。当一个对象被synchronized关键字修饰时,对象头中的锁信息被用于实现同步操作。当线程进入一个synchronized方法或代码块时,它首先会尝试获取对象的锁。如果锁是可用的,那么当前线程会成功获取锁并继续执行同步代码并且Java会将锁的计数器加1,表示当前线程获得了锁。如果锁已经被其他线程持有,那么当前线程就会进入阻塞状态,并将线程会放置到对象的等待集(Wait Set)中,其他线程释放锁并通知它即可继续执行。这种等待和唤醒的机制由监视器来管理。当线程执行完synchronized方法或代码块中的代码后释放锁,锁计数器减1。如果计数器为0,表示当前线程完全释放了锁,其他线程可以竞争获取该锁。

    2.监视器

            监视器(Monitor)是Java中用于实现同步的基本机制,用于保护对象的互斥访问。每个Java对象都与一个监视器相关联,监视器包含了与该对象关联的锁和等待集。只有持有锁的线程才能执行同步代码,其他线程必须等待锁的释放。监视器包括以下几个主要组成部分:

    1. 互斥锁(Mutex Lock):互斥锁是监视器的核心部分,用于实现对象的互斥访问。互斥锁是一个二进制状态变量,用于表示对象是否被锁定。它确保在任意时刻只有一个线程可以持有该对象的锁,其他线程必须等待锁的释放才能进入临界区。
    2. 等待集合(Wait Set):等待集合是一个线程的集合,用于存放因为等待对象锁而进入等待状态的线程。当线程调用对象的wait()方法时,它会被放入等待集合中,释放对象的锁,并进入等待状态。只有当其他线程调用notify()或notifyAll()方法时,等待集合中的线程才会被唤醒。
    3. 条件队列(Condition Queue):条件队列是一种用于线程通信的机制。每个监视器都可以关联一个或多个条件队列。线程可以调用条件队列的await()方法进入等待状态,并在满足特定条件时被唤醒。当线程被唤醒时,它会重新尝试获取对象的锁。
    4. 计数器(Counters):监视器中通常会包含一些计数器,用于记录等待线程的数量、锁重入的次数等信息。

            这些组成部分共同构成了监视器的基本结构,实现了线程的同步和互斥访问。它们允许线程在临界区内操作共享资源时按照一定的顺序访问,并确保线程间的互斥性和协调性。监视器的实现和具体细节由JVM负责,开发人员可以使用synchronized关键字或显式地调用监视器相关的方法来实现对象的同步。

    3.锁状态及锁升级

    在JDK 8中,Mark Word(标记字段)中的标记锁状态信息使用不同的位来进行标记。具体的标记位包括以下几个:

    1. 锁状态(Lock State):标记字段中的几个位用于表示对象的锁状态。其中,最低两位用于表示锁状态,可以有以下几种状态:
    • 00:无锁状态(Unlocked):对象未被任何线程锁定。
    • 01:偏向锁状态(Biased Lock):对象被某个线程偏向锁定。
    • 10:轻量级锁状态(Lightweight Lock):对象被某个线程轻量级锁定。
    • 11:重量级锁状态(Heavyweight Lock):对象被某个线程重量级锁定。
    1. 偏向线程ID(Thread ID):如果对象处于偏向锁状态(01),那么标记字段的一部分用于存储持有偏向锁的线程的ID。这样可以快速判断当前线程是否可以获取偏向锁。
    2. 偏向时间戳(Epoch):在JDK 8中,偏向锁引入了一个偏向时间戳,用于标记偏向锁的持续时间。当偏向锁的时间戳过期时,将会撤销偏向锁。

    标记字段的使用是为了支持JVM的锁优化技术,如偏向锁、轻量级锁和重量级锁,以提高同步操作的性能和效率。在Java中,锁状态的转换是由JVM自动处理的,根据线程的竞争情况和同步操作的结果来进行状态转换。锁状态的转换是为了提高同步操作的性能和效率,减少竞争带来的性能损失。锁状态按照如下顺序逐步增加:无锁-偏向锁-自旋锁-轻量级锁-重量级锁

    1. 无锁状态到偏向锁状态:当一个线程第一次访问一个对象时,对象处于无锁状态。如果JVM启用了偏向锁优化并且该线程获取到了锁,那么对象的状态会转换为偏向锁状态,同时标记字段中的偏向线程ID会记录当前线程的ID。
    2. 偏向锁状态到轻量级锁状态:如果一个线程在偏向锁状态下访问一个对象时,另一个线程也想要获取该对象的锁,那么偏向锁会被撤销,对象的状态会转换为轻量级锁状态。在这种情况下,JVM会尝试使用CAS(比较并交换)操作来尝试获取锁,如果成功则对象的状态转换为轻量级锁状态。
    3. 轻量级锁状态到重量级锁状态:如果轻量级锁获取锁的过程中发生竞争(即另一个线程也想要获取锁),那么轻量级锁会升级为重量级锁。升级为重量级锁的过程包括锁膨胀(Lock Inflation)和互斥同步(Mutex Synchronization),这样所有竞争的线程都需要通过互斥同步来获取锁。
    4. 重量级锁状态到无锁状态:当持有重量级锁的线程释放锁时,对象的状态会转换回无锁状态,等待其他线程再次竞争获取锁。

  • 相关阅读:
    java集合专题List接口ArrayList/Vector/LinkedList底层结构和源码分析
    网络程序设计——重叠I/O模型
    CockroachDB集群部署
    一文教你从Linux内核角度探秘JDK NIO文件读写本质(上)
    【网页设计】期末大作业html+css (个人生活记录介绍网站)
    【电力系统】基于两阶段鲁棒优化算法的微网多电源容量配置附matlab代码
    ffmpeg综合应用示例(五)——多路视频合并(Linux版本)
    HTML静态网页成品作业(HTML+CSS)——VIVO介绍网页(1个页面)
    uniapp 首页制作
    激光雷达数据的25个重要应用介绍
  • 原文地址:https://blog.csdn.net/rainyonelove/article/details/136345436