Synchronized关键字详解

news2024/11/23 21:51:55

1. Synchronized简介及使用

1.1 简介

在Java中,synchronized 是一个关键字,用于实现多线程环境下的同步控制,确保线程安全性。它可以应用于方法、代码块或静态方法上,提供了对临界区(共享资源)的互斥访问,防止多个线程同时访问和修改共享数据时出现数据不一致或其他并发问题。

synchronized 是 Java 中实现线程安全的基本方式之一,但它可能会带来一些性能开销。Java 5 之后,另外的同步机制 java.util.concurrent (JUC)下的锁机制更为灵活,比如 ReentrantLock,提供了更多控制和功能。

1.2 特点

  • 互斥性: synchronized 确保同一时间只有一个线程可以进入被 synchronized 保护的区域。

  • 锁的获取与释放: 当线程进入 synchronized 区域时,它尝试获取对象的锁。当离开 synchronized 区域时,会释放锁。

  • 递归性: Java 中的 synchronized 支持线程的递归锁,允许一个线程在持有锁的情况下再次获取该锁,而不会被自身所阻塞。

  • 对象监视器: synchronized 使用对象的监视器(monitor)来确保线程同步。对于方法而言,是当前实例对象;对于静态方法,是当前类的 Class 对象。

1.3 使用

它可以应用于方法、代码块或静态方法上,提供了对临界区(共享资源)的互斥访问,防止多个线程同时访问和修改共享数据时出现数据不一致或其他并发问题。

使用方法:

  1. 在方法上使用 synchronized:可以用在方法声明前,修饰非静态的方法,将方法设置为同步方法。这种情况下,锁是当前对象实例。
public synchronized void someMethod() {
    // 同步代码块
}

  1. 在代码块上使用 synchronized:也可以应用于代码块中,通过指定一个对象作为锁,来确保对某段代码的同步访问。
Object lock = new Object();
synchronized (lock) {
    // 同步代码块
}

  1. 静态方法中使用 synchronized:也可以用在静态方法声明前,修饰静态方法,将静态方法设置为同步方法。这种情况下,锁是类级别的,作用于整个类的所有实例。
public static synchronized void someStaticMethod() {
    // 静态同步方法
}

2. Synchronized原理1:底层实现

Synchronized的底层实现依赖于Monitor和与之关联的对象的对象头,所以要想了解Syncrronized必须先了解Monitor,还要了解对象的对象头信息,先来介绍一下对象的对象头:

2.1 对象头

在 Java 中,对象头(Object Header)是存储在每个对象实例中的一部分数据结构,用于管理对象的元数据信息。对象头位于对象实例数据之前,用于支持 Java 对象的各种特性和语言功能。
对象头通常包含以下信息:

  1. 哈希码(HashCode):

用于支持对象的哈希码,通常是对象的内存地址经过计算得到的值。

  1. 锁状态(Lock State):

用于存储对象的锁状态信息。在多线程环境下,这部分信息被用来实现 synchronized 关键字提供的锁机制,确保线程安全。

  1. 垃圾回收信息(Garbage Collection Information):

包括对象的标记状态、分代年龄、是否可回收等信息,用于支持 Java 的垃圾回收机制。

  1. 其他元数据信息:

可能还包括一些其他的元数据,比如指向类的元数据(class metadata)指针等。

对象头是 Java 对象的一部分,由 JVM 在运行时根据对象的类型和状态动态管理。对象头中存储的信息对于 Java 对象的生命周期、同步状态、哈希码计算以及垃圾回收等方面非常重要。

对象头中的具体结构和内容取决于具体的 JVM 实现和对象的状态,因此可能在不同的 JVM 实现中略有差异。对象头的管理对于 Java 的对象模型和内存管理是至关重要的。

对象头的结构:
请添加图片描述
其中Klass World是指向该对象所属类的引用,即指向方法区中的类对象。
请添加图片描述
数组对象相比普通对象对了一个array length 用来存储数组的长度。

对象头中Mark World的结构信息:

在这里插入图片描述

2.2 Monitor

2.2.1 Monitor简介

请添加图片描述

2.2.2 Monitor结构

请添加图片描述
Owner:指向持有锁的线程;
EntryList:指向一个阻塞队列,该队列存放因为获取不到该对象所进入阻塞状态的进程们;
MaitSet:指向等待状态的线程,即在Synchronized代码块中调用了该对象所得.wait()方法,那么当前线程会被放入这个WaitSet指向的集合中,当其中的某个对象被唤醒后,就进入EntryList,从而继续竞争锁资源

2.3 Synchornized的底层原理

2.3.1 Synchronized的原理如下:

请添加图片描述

2.3.2 举例说明:

当前线程2持有锁,具体来说:每一个被用来当做锁的对象,当某一个线程持有该对象锁时,其对象头中的Mark Word会被修改为重量级锁的类型,即下面的结构,其Mark Word的前30位指向操作系统提供弄得Monitor对象,而这个对象的hash code 分代年龄信息会保存到Monitor里面。
请添加图片描述
此时Monitor中的Owner会指向线程2,这时再有线程过来抢占锁,会进入Monitor的等待队列EntryList排队等待。
请添加图片描述
当线程2执行完临界区代码块,会恢复Mark Word的信息,即:
请添加图片描述
线程2释放锁资源,执行结束。
这时线程1抢占了锁,Monitor中的Owner就会指向线程1,此时,线程1就是锁的持有者,而线程3需继续等待,直到线程1释放了锁。
需要注意的是:线程2释放锁资源后,会唤醒处于阻塞队列上的线程,线程1和线程3并不是按照先来后到的原则去抢占锁的,因为Synchronized是非公平锁
请添加图片描述

2.3.3 再从字节码角度理解一下执行逻辑:

请添加图片描述
请添加图片描述

2.3.4 总结

请添加图片描述

3. Synchronized原理2:锁优化

3.1 锁的概念

3.1.1 重量级锁

重量级锁是指 Java 中的重量级同步锁(使用Monitor实现的即是重量级锁),也称为传统的互斥同步。它是 synchronized 关键字所使用的一种锁状态,用于在多线程环境下保护共享资源,确保在同一时间只有一个线程能够进入同步代码块执行。
特点包括:

  • 互斥性: 重量级锁是一种排他性锁,当一个线程获得锁并进入同步代码块时,其他线程必须等待持有锁的线程释放锁,才能继续执行同步代码块。

  • 基于操作系统的阻塞: 当多个线程竞争同一个锁时,锁会升级为重量级锁。这时会涉及到操作系统层面的内核态的线程阻塞与唤醒。

  • 性能开销: 重量级锁的性能开销相对较大。因为它涉及到线程的阻塞和唤醒,频繁的上下文切换和内核态和用户态的切换,会增加系统的开销。

  • 适用范围: 适用于多线程竞争激烈的情况,或者同步块执行时间较长的情况,此时引入重量级锁能够确保线程安全。

重量级锁是在多线程环境中确保线程安全的传统锁机制,但在一些情况下由于性能开销比较大,可能会影响系统的并发性能。因此,Java 在锁升级的过程中会尽量避免将锁直接升级为重量级锁,而会尝试将锁保持在偏向锁或轻量级锁状态,以提高多线程环境下的性能表现。

3.1.2 轻量级锁

轻量级锁(Lightweight Locking)是 Java 中用于优化同步锁性能的一种锁状态。它是为了解决多线程环境下对同步锁竞争的性能问题而引入的优化方案。

  • 特点包括:
    • 自旋锁: 轻量级锁采用乐观锁的思想,在竞争不激烈的情况下,采用自旋锁避免线程进入阻塞状态。

    • CAS 操作: 使用 Compare and Swap(CAS)操作来避免多线程竞争。当只有一个线程持有锁,其他线程会使用 CAS 操作尝试获取锁,而不会直接进入阻塞状态。

    • 标识对象: 轻量级锁通过在对象头中的 Mark Word 部分设置标志位,用于记录当前锁的状态。这个标志位包含了指向线程栈中锁记录(Lock Record)的指针。

    • 升级为重量级锁: 如果多个线程尝试获得锁,或者自旋一定次数后仍然无法获得锁,轻量级锁会升级为重量级锁。

轻量级锁在多线程环境下提供了更好的性能,特别是在线程竞争不激烈的情况下,避免了线程阻塞和唤醒的开销。它减少了对系统级锁的使用,减轻了多线程并发竞争带来的性能损耗。然而,当竞争激烈或自旋次数过多时,会导致轻量级锁升级为重量级锁,这时就会带来与传统锁相似的性能开销。

3.1.3 偏向锁

偏向锁(Biased Locking)是 Java 中针对单线程访问同步块的一种优化机制。它旨在减少不必要的同步操作,提高没有竞争的场景下的性能。

  • 特点包括:
    • 针对单线程访问: 偏向锁假设一个对象在没有竞争的情况下,会被同一个线程多次访问。因此,当对象被初始化后,它会认为会被同一个线程多次访问,从而偏向于该线程。

    • 记录线程 ID: 偏向锁会在对象头中记录持有锁的线程 ID。当线程再次访问该对象时,会直接获取锁,而不需要进行竞争。

    • 撤销机制: 如果其他线程尝试访问已经偏向的对象,偏向锁就会失效,升级为轻量级锁。

    • 适用范围: 适用于大部分情况下只有一个线程访问对象的场景,比如很多情况下对象的同步都是由单个线程访问的。

偏向锁的引入主要是为了解决大部分情况下是单线程访问共享资源的场景下,避免无谓的同步操作。因为在很多场景下,对象在初始化后,会被同一个线程多次访问,此时偏向锁能够提供较好的性能优化。不过,如果对象在多线程环境下被频繁访问,偏向锁的优化机制反而会增加性能开销,因为会频繁失效和撤销。

3.1.4 自旋锁

自旋锁是一种用于多线程同步的锁机制,在这种锁中,线程在尝试获取锁时不会立即被阻塞,而是会进行短暂的“忙等”(自旋)尝试获取锁。自旋锁在一定程度上避免了线程阻塞所带来的开销,特别是在锁被占用时间较短、并发程度不高的情况下能够提供更好的性能。

  • 主要特点:
    • 忙等: 线程在尝试获取锁时会不断地进行循环检查,而不是立即阻塞等待。
    • 减少线程切换: 在短时间内争夺锁的情况下,自旋锁避免了线程频繁进入阻塞和唤醒状态所带来的系统开销。
    • 限时自旋: 一些自旋锁支持限时自旋,即在一定时间内自旋未成功时,线程会进入阻塞状态,避免长时间的忙等。

在Java中,synchronized锁的优化就使用了自旋锁的思想。偏向锁和轻量级锁都是基于自旋锁的概念进行设计的。自旋锁能够在一定程度上提升并发性能,但在高并发、长时间持有锁的情况下,自旋锁可能会消耗大量的CPU资源。因此,在使用自旋锁时,需要根据实际场景进行合理的调整和使用。

3.2 Synchronized的锁优化

synchronized 关键字在 Java 中通过不断的优化来提高性能和并发处理效率。在不同的 Java 版本中,对于 synchronized 的优化有所不同。

  • Java 6 以前的优化:
    1. 重量级锁优化: 在 Java 6 以前,synchronized 锁是重量级锁,它会引入用户态和内核态的切换,涉及线程的阻塞和唤醒,性能开销相对较大。
  • Java 6 及之后的优化:
    1. 偏向锁和轻量级锁: 引入了偏向锁和轻量级锁,用于优化对于同步块的访问。

      • 偏向锁: 用于表示对象被偏向于一个线程。在没有竞争的情况下,对象会偏向于第一个访问它的线程,避免了不必要的同步操作。
      • 轻量级锁: 在多个线程访问同步块时,偏向锁会升级为轻量级锁。这时,通过自旋锁避免线程的阻塞,避免了用户态和内核态的切换。
    2. 自适应自旋:

      • 对轻量级锁,Java 运行时会根据锁竞争情况自适应地调整自旋的次数,以提高性能。
      • 针对重量级锁的竞争状态,未获取到锁的线程不会立即进入阻塞状态,而是尝试循环获取锁,也就是自旋优化,防止进入阻塞状态引起的用户态和内核态的切换,Java 运行时会根据锁竞争情况自适应地调整自旋的次数,以提高性能。

这些优化的引入旨在在没有竞争的情况下减少不必要的同步操作,并尽量减少线程的阻塞,提高并发性能。同时,当竞争激烈时,优化也使得锁能够升级为重量级锁,确保线程安全。这些优化机制让 synchronized 的性能得到了较大的提升。

3.3 Synchronized的锁优化原理

首先来看一个小故事,加深对锁优化的理解:
请添加图片描述
请添加图片描述

3.3.1 轻量级锁
  1. 轻量级锁的加锁原理如下:

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

  1. 轻量级锁的解锁原理如下:

请添加图片描述
请添加图片描述

  1. 总结如下:
    请添加图片描述
3.3.2 锁膨胀

请添加图片描述

请添加图片描述
请添加图片描述
请添加图片描述

3.3.3 自旋优化

在这里插入图片描述
请添加图片描述
请添加图片描述

3.3.4 偏向锁

请添加图片描述
请添加图片描述
请添加图片描述

3.3.4.1 偏向状态

请添加图片描述
请添加图片描述
默认对象头如下:
请添加图片描述
请添加图片描述

请添加图片描述
请添加图片描述

调用对象的hashcode也会禁用偏向锁,因为hashcode与偏向锁的markword不是同一个,两者不能同时存在:
请添加图片描述

3.3.4.2 偏向撤销

请添加图片描述
请添加图片描述

请添加图片描述
修改代码如下:让两个线程交替执行,测试从偏向锁->轻量级锁
请添加图片描述
执行结果如下:
请添加图片描述
请添加图片描述
只有重量级锁才有wait/notify,故使用wait/notify必须要升级为重量级锁。

3.3.4.3 批量重偏向

请添加图片描述
请添加图片描述
请添加图片描述

3.3.4.4 批量撤销

请添加图片描述

3.3.5 锁消除

锁消除是指在编译器或运行时进行的一种优化技术,用于消除不必要的同步锁。这种优化技术的目的是提高性能,减少不必要的锁竞争和同步开销。

如何实现锁消除:
在Java中,JIT编译器或者运行时会对代码进行分析,识别出某些同步块中,由于上下文情况的确定,在并不会存在多线程竞争的情况下,可以安全地去除同步锁,从而避免不必要的同步操作。这个过程被称为锁消除。

举例说明:

public String concatenateString(String s1, String s2, String s3) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    sb.append(s3);
    return sb.toString();
}

在这段代码中,StringBuffer 是线程安全的,但是在这个方法中,sb 只在当前线程内部使用,不会被其他线程访问。因此,JIT 编译器可以进行锁消除,将sb 对象的同步锁去除,因为在这个上下文中并不存在多线程竞争,不会产生线程安全问题。
再例如如下代码:
请添加图片描述
由于方法b()里面的同步代码块锁的是一个局部对象,非共享的,这样加锁和不加锁没什么区别,会被即时编译器优化掉,即会优化消除锁。
结果对比如下:
在这里插入图片描述

锁消除的意义:

1. 减少不必要的同步开销,提高程序性能。
2. 避免因为过多同步锁导致的系统开销和性能下降。

锁消除是一种重要的优化技术,但需要确保在消除锁的情况下不会引入潜在的线程安全问题。因此,它通常是在编译器和运行时的静态和动态分析中进行的,只有在确定情况下才会执行锁消除操作。

3.3.6 锁优化原理总结

synchronized 锁在 Java 中经历了不断的优化,主要包括偏向锁、轻量级锁和重量级锁等不同的优化状态。这些优化状态是为了在不同场景下提供更高效的同步机制。

锁优化原理包括:

  1. 偏向锁(Biased Locking):

    • 初始阶段,对象会被设置为偏向于第一个访问它的线程。当只有一个线程访问对象时,偏向锁能够提供低延迟的同步。
    • 目的是为了在没有竞争的情况下,减少不必要的同步开销。
  2. 轻量级锁(Lightweight Locking):

    • 在出现轻量级竞争的情况下,偏向锁会升级为轻量级锁。这时,会使用 CAS 操作来避免线程阻塞。
    • 目的是为了避免频繁地阻塞和唤醒线程,在轻量级竞争的情况下提供更好的性能。
  3. 重量级锁(Heavyweight Locking):

    • 当多个线程竞争同一个锁时,锁会升级为重量级锁,即传统的互斥同步。这时会涉及到操作系统层面的内核态的线程阻塞与唤醒。
    • 目的是在激烈竞争的情况下,保证线程安全。

这些优化状态的转换,例如从偏向锁到轻量级锁,或者从轻量级锁到重量级锁,是根据竞争情况来决定的。Java 中的锁优化机制旨在提供更高效的同步机制,以适应不同程度的并发竞争,从而提高系统的性能。

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

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

相关文章

Python知识点——高维数据的格式化

常用JSON格式对高维数据进行表达和存储&#xff1a; 常见的高维数据最典型的例子&#xff1a;<key,value>键值对 JSON格式表达键值对<key, value>的基本格式如下&#xff0c;键值对都保存在双引号中&#xff1a; "key" : "value" Json库 dump…

前后端交互常见的几种数据传输格式 form表单+get请求 form表单+post请求 json键值对格式

目录 1. get请求 query string 2.form表单get请求 3..form表单post请求 4..json格式 5.总结 1. get请求 query string 前端通过get请求携带 query string&#xff08;键值对&#xff09; ,后端通过req.getParameter(key)方法获取数据。如果key不存在&#xff0c;获取到的就…

苹果手机钱包怎么付款?教你如何使用Apple Pay支付

苹果钱包是iPhone手机的一项实用功能&#xff0c;可以将银行卡、信用卡、交通卡、学生证等收纳在其中。在日常生活中&#xff0c;手机支付变得越来越方便。那么&#xff0c;如何在苹果手机上使用Apple Pay进行支付呢&#xff1f;苹果手机钱包怎么付款&#xff1f;下面请跟着小编…

Redis小记(一)

NoSQL数据库简识 引入 随着web时代的到来&#xff0c;PC端和移动端的用户越来越多&#xff0c;之前的单体服务器已经承载不住这么大量的访问请求操作&#xff0c;如今就演化成了下图这种&#xff0c;通过ngnix负载均衡&#xff0c;将请求以分摊到多个不同的服务器上&#xff…

微信小程序rich-text里面写单行溢出显示省略号在ios中不显示的问题

项目用uniapp开发。然后赋值用v-html来写。&#xff08;v-html可以转换html格式并且展示。运行到小程序里面是用rich-text标签展示&#xff09; 原来返回的item.titleName如下&#xff1a;<font style"color: #ff4343;">测试</font>课程名称长度加加加爱…

【微信小程序】新版获取手机号码实现一键登录(uniapp语法)(完整版附源码)

需求 如图&#xff0c;点击按钮&#xff0c;获取用户手机号实现一键登录&#xff0c;当然&#xff0c;用户也可以自行输入其他手机号进行登录 问题 要想获取用户手机号并不复杂&#xff0c;但由于近几年微信小程序获取手机号的api进行了更新&#xff0c;当前很多帖子使用的…

【delphi】中 TNetHTTPClient 注意事项

一、TNetHTTPClient 是什么&#xff1f; 用于管理 HTTP 客户端的组件。相当于indy中的TidHTTP控件&#xff0c;是实现HTTP请求的客户端控件。 二、TNetHTTPClient 需要注意什么&#xff1f; 需要注意的是几个Timeout&#xff0c;因为我们使用TNetHTTPClient控件的时候&#x…

1 快速了解Paimon数据湖核心原理及架构

1.1 什么是Apache Paimon Apache Paimon的前身属于Flink的子项目&#xff1a;Flink Table Store。 目前业内主流的数据湖存储项目都是面向批处理场景设计的&#xff0c;在数据更新处理时效上无法满足流式数据湖的需求&#xff0c;因此Flink社区在2022年的时候内部孵化了 …

Golang Windows系统使用make build

gcc -v 找到上面mingw64/bin目录&#xff0c;复制mingw32-make.exe一份&#xff0c;改成make.exe ,即可。

9.spark自适应查询-AQE之动态调整Join策略

目录 概述动态调整Join策略原理实战 动态优化倾斜的 Join原理实战 概述 broadcast hash join 类似于 Spark 共享变量中的广播变量&#xff0c;Spark join 如果能采取这种策略&#xff0c;那join 的性能是最好的 自适应查询AQE(Adaptive Query Execution) 动态调整Join策略 原…

金蝶云星空单据转换下推时上游单据的主键和明细主键获取和保存

文章目录 金蝶云星空单据转换下推时上游单据的主键和明细主键获取和保存产品序列号对照表增加字段创建单据转换插件在单据转换规则注册插件测试 金蝶云星空单据转换下推时上游单据的主键和明细主键获取和保存 比如&#xff1a;售后单下推对照表&#xff0c;是一对一&#xff0…

(深度全面解析)ChatGPT的重大更新给创业者带来了哪些红利机会

hi&#xff0c;同学们&#xff0c;我是赤辰 7月份的时候&#xff0c;Open AI就找来了一位全球顶级的华人产品经理Peter Deng&#xff0c;要给这个技术很强但交互很差ChatGPT动一番大手术。 在11月7日凌晨2点&#xff0c;终于等到了Open AI发布的首届开发者大会&#xff0c;也被…

Vb6 TCP Server服务端监听多个RFID读卡器客户端上传的刷卡数据

本示例使用设备介绍&#xff1a;WIFI无线4G网络RFID云读卡器远程网络开关物流网阅读器TTS语音-淘宝网 (taobao.com) Option ExplicitConst BUSY As Boolean False 定义常量 Const FREE As Boolean TrueDim ConnectState() As Boolean 定义连接状态 Dim ServerSendbuf(…

超级好用的几个工具

JamTools JamTools是一个全平台支持的小工具集软件&#xff0c;可用于Windows 7/8/10/11、MacOS、Ubuntu等系统&#xff08;其他系统可自行编译源码进行打包&#xff09;。该工具集包含了多项实用功能&#xff0c;如滚动/区域截屏、录屏、文字识别、多语言互译、多媒体格式转换…

计算机网络——物理层-物理层的基本概念、物理层下面的传输媒体

目录 物理层的基本概念 传输媒体 物理层的基本概念 在计算机网络中&#xff0c;用来连接各种网络设备的传输媒体种类众多。大致可以分为两类。一类是导引型传输媒体&#xff0c;另一类是非导引型传输媒体。 在导引型传输媒体中&#xff0c;常见的有双绞线、同轴电缆、光纤。…

Qt开发流程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…

是时候和 Confluence 说再见了

作为最早为 Confluence 中文化提供服务的社区&#xff0c;我们也面临着最后的时间了。 Confluence 已经不再为用户签发开源许可证了&#xff0c;这意味着在今年许可证到期后&#xff0c;我们要不就需要把所有数据迁移到 Confluence 云平台上。 要不就自己部署完整的云平台服务…

Apache Airflow (一) : Airflow架构及

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

three.js点滴yan(整理后)

场景、相机和渲染器 Three.js整个系统主要包含场景Scene、相机Camera和WebGL渲染器WebGLRenderer三大块&#xff0c;其中场景又包含模型和光源。WebGL渲染器的主要作用就是把相机对应场景渲染出来&#xff0c;显示在网页Cnavas画布上。 Three.js源码 Three.js各个构造函数对应…

YOLO目标检测——交通标志检测数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;交通标志识别数据集在自动驾驶、交通安全监控、智能交通系统、驾驶员辅助系统和城市规划等领域都有广泛应用的潜力数据集说明&#xff1a;交通标志识别数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含有交通标识3类…