并发-基础概念

197 阅读5分钟

本文主要介绍并发编程相关基础概念

锁是一种用于操作系统和计算机程序的机制,用来解决共享资源的互斥访问问题

锁分类

1. 可重入锁和非可重入锁

可重入锁:一个线程获取锁后,再次请求同一个锁时,仍然能获取该锁。synchronized关键字和ReentrantLock都是可重入锁。
非可重入锁:一个线程获取锁后,再次请求同一个锁时,将无法获取锁,会发生死锁。

2. 公平锁和非公平锁

公平锁:按照线程请求锁的顺序来分配。正如它的名字,是比较公平的。
非公平锁:只要锁处于未锁定状态,无论什么时候来请求锁,只要获取到锁可用状态就获取锁。ReentrantLock(默认)和synchronized都是非公平锁。

3. 互斥锁和共享锁

互斥锁:只允许一个线程持有。synchronized和ReentrantLock都是互斥锁。
共享锁:允许多个线程持有,但同一时间只允许一个线程执行。ReentrantReadWriteLock支持多个线程同时读,也支持一个线程写入。

4. 悲观锁和乐观锁

悲观锁:假设肯定会有冲突,每次读取数据前都会上锁。synchronized和ReentrantLock都是悲观锁。
乐观锁:假设不会有冲突,读写时不上锁,在更新数据前判断数据是否确实没有更新。利用版本号、CAS等机制实现。

死锁

死锁(Deadlock)指的是在多线程或进程编程中,两个或多个线程或进程因互相请求对方占用的资源而相互等待的一种情况。在这种情况下,每个线程或进程都在等待其他线程或进程释放它所需要的资源,导致所有线程或进程都无法继续执行。
一个典型的死锁场景是两个线程或进程分别持有一些资源,并且都需要获取对方持有的资源才能继续执行。如果它们在等待对方释放资源的同时,不释放自己的资源,那么就会发生死锁。

1. 产生死锁的条件

  • 互斥条件(Mutual Exclusion):至少有一个资源必须处于独占模式,即一次只能被一个进程或线程占用。
  • 请求与保持条件(Hold and Wait):一个进程或线程可以在等待其他资源的同时保持已经占用的资源,即它不释放已经占用的资源。
  • 不剥夺条件(Non-preemption):资源不能被强制性地从占用它的进程或线程中剥夺,只能由持有者显式地释放。
  • 循环等待条件(Circular Wait):存在一个进程或线程的等待序列,使得每个进程或线程都在等待下一个进程或线程所占用的资源。

2. 解锁死锁

  • 资源预分配:在程序运行之前,预分配所需资源,避免在运行时发生死锁。
  • 资源释放顺序规定:规定资源的申请和释放顺序,避免进程或线程持有多个资源的情况。
  • 死锁避免算法:根据系统状态和资源请求情况,采取预测和避免死锁的策略,例如银行家算法等。
  • 死锁检测和恢复:定期检测系统状态,发现死锁后,采取恢复策略,例如终止某个进程或线程,回收资源等。

JMM(JAVA内存模型)

Java 内存模型(Java Memory Model,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了 Java 程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

image.png MM 是一种规范,是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性

原子性:

在 Java 中,为了保证原子性,提供了两个高级的字节码指令 Monitorenter 和 Monitorexit。这两个字节码,在 Java 中对应的关键字就是 Synchronized。
因此,在 Java 中可以使用 Synchronized 来保证方法和代码块内的操作是原子性的。

可见性:

Java 中的 Volatile 关键字修饰的变量在被修改后可以立即同步到主内存。被其修饰的变量在每次使用之前都从主 内存刷新。因此,可以使用 Volatile 来保证多线程操作时变量的可见性。除了 Volatile,Java 中的 Synchronized 和 Final 两个关键字也可以实现可见性。只不过实现方式不同

有序性

在 Java 中,可以使用 Synchronized 和 Volatile 来保证多线程之间操作的有序性。
区别: Volatile 禁止指令重排。
Synchronized 保证同一时刻只允许一条线程操作。

volatile底层实现

作用:

保证数据的“可见性”:被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读 的现象。 禁止指令重排:在多线程操作情况下,指令重排会导致计算结果不一致

底层实现:

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令” lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;
  2. 它会强制将对缓存的修改操作立即写入主存;
  3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。

happens-before

用来描述和可见性相关问题:如果第一个操作 happens-before 第二个操作,那么我们就说第一个操作对于第二 个操作是可见的 常见的happens-before:volatile 、锁、线程生命周期