JavaDriver JavaDriver
首页
  • 基础
  • 并发
  • JVM
  • 设计模式
  • 计算机网络
  • 操作系统
  • 数据结构
  • 算法
  • MYSQL
  • REDIS
  • Netty
  • Kafka
系统设计
非技术
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

YoungAnn

西二旗Java老司机一枚 致力于社会主义添砖Java
首页
  • 基础
  • 并发
  • JVM
  • 设计模式
  • 计算机网络
  • 操作系统
  • 数据结构
  • 算法
  • MYSQL
  • REDIS
  • Netty
  • Kafka
系统设计
非技术
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • 并发

    • 线程池是如何实现的?
    • 简述 CAS 原理,什么是 ABA 问题,怎么解决?
    • 简述 Synchronized,Volatile,可重入锁的不同使用场景及优缺点
      • Java中的内存可见性
      • Volatile 的实现原理
      • Synchronized 的用法
      • Synchronized 的锁存储在哪里?
      • 为什么Java中每个对象都可以作为锁?
      • Synchronized 锁升级
        • 偏向锁
        • 轻量级锁
        • 重量级锁
        • 锁升级
        • 锁的优缺点对比
      • Java如何实现原子操作
        • CAS实现原子操作
        • CAS 存在的三个问题
        • 锁机制实现原子操作
    • Synchronized 与 Lock 相比优缺点分别是什么?
    • 重入锁是如何实现的?
    • volatile 关键字解决了什么问题,它的实现原理是什么?
    • 简述 Java 锁升级的机制
    • 简述 Java AQS 的原理以及使用场景
    • 什么是公平锁?什么是非公平锁?
    • Java 的线程有哪些状态,转换关系是怎么样的?
    • Java 是如何实现线程安全的,哪些数据结构是线程安全的?
    • 手写死锁
    • 为什么我们不能直接调用 run() 方法?
    • Java 线程有哪些常用方法?
    • 手写生产者消费者模型
    • ThreadLocal 实现原理是什么?为什么要使用弱引用?
  • JVM

  • 设计模式

  • Java相关
  • 并发
YoungAnn
2022-04-04
目录
Java中的内存可见性
Volatile 的实现原理
Synchronized 的用法
Synchronized 的锁存储在哪里?
为什么Java中每个对象都可以作为锁?
Synchronized 锁升级
偏向锁
轻量级锁
重量级锁
锁升级
锁的优缺点对比
Java如何实现原子操作
CAS实现原子操作
CAS 存在的三个问题
锁机制实现原子操作

简述 Synchronized,Volatile,可重入锁的不同使用场景及优缺点

在多线程编程中 Synchronized,Volatile 都扮演着重要角色, 都已用来实现原子操作。 Volatile 是轻量级的 Synchronized ,保证了共享变量的可见性。

可见性的意思是:当线程A修改共享变量的值后,线程B能立刻读到这个修改后的值。

Volatile 不会引起线程上下文的切换和调度,如果使用的恰当,会比 Synchronized 执行成本更低。

# Java中的内存可见性

  • 可见性:一个线程对共享变量值的修改,能够及时被其他线程看到。

  • 共享变量:如果一个变量在多个线程的工作内存中都存在副本,那这个变量就是这几个线程的共享变量。

  • Java内存的规定:

-线程对共享变量的所有操作都必须在自己的工作内存中进行,不可直接从主内存中读写; -不同线程之间无法直接访问其他线程工作内存中的变量,线程间的变量值的传递需要通过主内存。

# Volatile 的实现原理

如果对用 Volatile 修饰的变量写操作,JVM 会向处理器发出一条 Lock 前缀的指令,Lock 前缀的指令在多核处理器下会引发两件事情:

  1. 将当前处理器缓存行的数据写会到系统内存

  2. 这个写会内存的操作会使其他缓存中的该内存地址的数据无效

# Synchronized 的用法

  1. 普通同步方法:锁当前实例对象

public synchronized void method() {

}

1
2
3
4
5
  1. 静态同步方法:锁当前类的Class对象

public synchronized static void method() {

}

1
2
3
4
5
  1. 同步代码块:锁括号里对象

public void method()

{

synchronized(this) {

}

}

1
2
3
4
5
6
7
8
9
10
11

# Synchronized 的锁存储在哪里?

Synchronized的锁存储在Java的对象头里。

Java 对象头里的 Mark Word 用于存储对象的 HashCode 、分代年龄和锁标记位。

32位虚拟机中, Mark Word 的存储结构如下:

在运行期, Mark Word 有四种状态:轻量级锁、重量级锁、GC 标记、偏向锁,各状态下的存储结构如下图:

在64位虚拟机下,Mark Word 的存储结构如下图:

# 为什么Java中每个对象都可以作为锁?

任何对象都有一个 monitor 与之关联,当一个 monitor 被持有后,他将处于锁定状态。monitor 是用 C++ 实现的。

同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到同步块的结束处和异常处,JVM 要保证每个 monitorenter 都有 monitorexit 与之配对。

# Synchronized 锁升级

Synchronized 一直被称为重量级锁。但是在JDK 1.6之后它已经变得不那么重了。JDK 1.6 对Synchronized 的优化点在于:

  • 引入了偏向锁

  • 引入了轻量级锁

在JDK 1.6 中,Synchronized 锁有四种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁、重量级锁。

这几个状态会随着锁竞争升级,但是不可以降级。

# 偏向锁

为什么引入偏向锁?

不存锁竞争,或者总是由同一线程多次获得锁的场景,偏向锁的代价更低。

当一个线程访问同步块并获取到锁时,在锁对象头记录该线程的id,以后该线程进入和退出该同步块时不需要CAS来加锁和解锁。

偏向锁何时释放?

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。

偏向锁一定起到正面作用吗?

不是的。

偏向锁的适用场景是:不存锁竞争,或者总是由同一线程多次获得锁的场景。

如果你确定你的程序中 锁通常处于竞争状态,可以通过JVM参数关闭偏向锁。关闭后,程序回魔人进入轻量级锁状态。


-XX:UseBiasedLocking=false

1
2
3

# 轻量级锁

轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋(关于自旋的介绍见文末)的形式尝试获取锁,线程不会阻塞,从而提高性能。

轻量级锁的获取主要由两种情况:① 当关闭偏向锁功能时;② 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。

轻量级锁何时升级为重量级锁?

若当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁(锁膨胀)。

另外,当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁(锁膨胀)。

# 重量级锁

重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。

重量级锁的获取是释放一般会有线程上下文切换,代价是比较大的,所以说是重量级锁。

# 锁升级

# 锁的优缺点对比

# Java如何实现原子操作

原子操作:不可被中断的一个或一系列操作。

Java 有两种实现原子操作的方式:CAS(compare and swap)、锁。

# CAS实现原子操作

CAS理论是 juc 包实现的基石,在intel的CPU中,CAS 通过调用本地方法(JNI)使用cmpxchg指令来实现的非阻塞算法。对比于synchronized阻塞算法,基于 CAS 实现的 juc 在性能上有了很大的提升。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。

# CAS 存在的三个问题

  1. ABA问题
  • 因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

  • ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

  • 从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

  1. 循环时间长开销大

  2. 只能保证一个共享变量的原子操作

从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

# 锁机制实现原子操作

锁机制保证了只有获得锁的线程才能操作指定的内存区域。除了偏向锁,JVM实现锁的方式都使用了循环CAS。

编辑 (opens new window)
上次更新: 2022/05/19, 21:26:01
简述 CAS 原理,什么是 ABA 问题,怎么解决?
Synchronized 与 Lock 相比优缺点分别是什么?

← 简述 CAS 原理,什么是 ABA 问题,怎么解决? Synchronized 与 Lock 相比优缺点分别是什么?→

最近更新
01
电商-商品系统设计
12-17
02
关于如何写OKR
12-09
03
对事不对人 vs 对人不对事
12-09
更多文章>

Gitalking ...

Theme by Vdoing | Copyright © 2022-2025 YoungAnnn | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式