问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Synchronized和ReentrantLock锁的区别

创作时间:
作者:
@小白创作中心

Synchronized和ReentrantLock锁的区别

引用
CSDN
1.
https://blog.csdn.net/weixin_44183847/article/details/134472693

在Java并发编程中,锁机制是确保线程安全的重要手段。本文将深入探讨synchronized和ReentrantLock这两种常用的锁机制,分析它们的实现原理、优缺点以及适用场景。

1. 锁机制概述

1.1 如何实现线程安全?

实现线程安全的主要方式包括:

  • 加锁:包括悲观锁和乐观锁
  • 原子类(CAS+自旋)
  • ThreadLocal

其中,CAS(Compare and Swap)是“比较并交换”的缩写,AQS(AbstractQueuedSynchronizer)是“排队同步器”的缩写。

1.2 synchronized

synchronized可以用于实例方法、静态方法和代码块,具有可重入性和互斥性。

1.2.1 synchronized 锁实现

Java对象的锁机制是通过monitor对象实现的。每个Java对象都有一个与之关联的monitor对象,它在JVM中用C++实现,负责锁机制和CPU硬件的交互。monitor对象包含以下组件:

  • owner:标识当前持有对象锁的线程ID,如果没有线程持有锁,则值为null。
  • entryList:记录正在请求锁但尚未获得锁的线程。
  • waitSet:记录通过wait方法等待的线程。

当线程获取锁后,计数器会+1,重复获取锁时计数器会持续增加。释放锁时计数器会-1,直到为0时其他线程才能获取锁。

1.2.2 synchronized 锁升级演变

为了解决synchronized的性能问题,JDK1.6引入了锁升级机制,根据线程竞争程度设计了不同类型的锁:

1.2.3 Java 对象结构

Java对象的内存布局主要包括:

  • 对象头
  • mark word:存储对象的运行时数据,如哈希码、GC分代年龄、锁状态标志等。
  • class pointer:指向对象的类元数据。
  • 对象中的实际数据:存储对象的变量和内部对象。
  • 对齐填充:用于内存对齐,提高访问速度。

1.2.4 mark word

mark word在不同锁状态下的存储信息如下:

  • 无锁:对象创建后,没有线程访问时的状态。
  • 偏向锁:当一个线程首次访问对象时,会将mark word标记为偏向锁,并记录线程ID。
  • 轻量级锁:当另一个线程尝试获取锁时,会通过CAS操作尝试获取锁。
  • 重量级锁:当多个线程竞争锁时,会升级为重量级锁,使用操作系统提供的互斥量进行同步。

1.2.5 锁升级过程

锁升级过程如下:

  1. 无锁:对象创建后,没有线程访问时的状态。
  2. 偏向锁:当一个线程首次访问对象时,会将mark word标记为偏向锁,并记录线程ID。
  3. 轻量级锁:当另一个线程尝试获取锁时,会通过CAS操作尝试获取锁。
  4. 重量级锁:当多个线程竞争锁时,会升级为重量级锁,使用操作系统提供的互斥量进行同步。

锁升级是JVM自动管理的,且升级过程不可逆。

1.3 ReentrantLock

ReentrantLock的内部结构如下:

  • Sync:同步锁的基础类。
  • FairSync:公平同步锁。
  • NonfairSync:非公平同步锁。

ReentrantLock默认是非公平锁,可以通过构造函数指定公平锁。ReentrantLock底层使用CAS操作实现,但整体设计上更偏向于悲观锁,提供了更多的功能和灵活性,如可重入性、可中断性和公平性选择。

1.4 synchronized 和 ReentrantLock 对比

  • 相同点

  • 都是悲观锁

  • 都具有可重入性

  • 区别

  • 加锁方式:synchronized通过操作系统层面的互斥变量mutex实现,而ReentrantLock基于CAS操作实现。

  • 可中断性:ReentrantLock支持可中断的锁获取,而synchronized不支持。

  • 公平性:ReentrantLock可以选择公平或非公平锁,而synchronized默认是非公平锁。

1.5 为什么synchronized比较慢?AQS是如何解决的?

synchronized的性能问题主要在于:

  • 性能开销:每次获取和释放锁都需要通过操作系统内核态进行,导致上下文切换和系统调用开销。
  • 缺乏灵活性:synchronized是一种悲观锁,一旦有线程进入临界区,其他线程都要被阻塞。

AQS(AbstractQueuedSynchronizer)通过以下方式优化了锁的实现:

  • 队列管理:使用FIFO队列管理等待获取同步状态的线程。
  • CAS操作:减少对操作系统层面锁操作的依赖,降低性能开销。
  • 灵活性:支持独占锁、共享锁等不同类型的同步器实现。

1.6 tomcat 和 spring 线程池为什么要分开

Tomcat和Spring的线程池分开主要是为了解耦:

  • Tomcat线程池:用于处理客户端请求。
  • Spring线程池:用于处理业务逻辑。

这种分离使得系统架构更加清晰,便于管理和优化。

2. 原子类

2.1 AtomicLong 和 LongAdder

AtomicLong和LongAdder都是用于原子更新long类型值的工具类,但它们有一些区别:

  • 竞争热点:AtomicLong存在单一的竞争点,所有线程都竞争同一把锁。而LongAdder通过分段存储减少竞争。
  • 内部结构:AtomicLong使用CAS操作实现单个long值的原子更新,而LongAdder使用线程本地的long值数组。
  • 空间占用:AtomicLong占用固定内存,而LongAdder在高并发场景下可能占用更多内存。

示例代码:

// 使用 AtomicLong 进行原子操作
AtomicLong atomicLong = new AtomicLong();
atomicLong.incrementAndGet();

// 使用 LongAdder 进行原子操作
LongAdder longAdder = new LongAdder();
longAdder.increment();

总结来说,AtomicLong适合低竞争场景,而LongAdder适合高并发场景。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号