博客
关于我
面试必问的多线程-1.4:各种锁介绍
阅读量:681 次
发布时间:2019-03-15

本文共 2652 字,大约阅读时间需要 8 分钟。

Java多线程锁机制:从基础到高级锁的全面解析

在Java多线程编程中,锁机制是确保多线程环境下资源安全的核心机制。不同的锁类型不仅能够满足不同的同步需求,还能优化程序性能。本文将从偏向锁到重入锁,再到自旋锁、共享锁、读写锁、公平锁、非公平锁、死锁和活锁,全面解析Java中的锁机制。


1. 偏向锁机制

偏向锁(Bias Lock)是Java 6引入的一项多线程优化机制。其核心思想是:如果一个线程在运行过程中一直持有锁资源,并且没有遇到其他线程抢占锁的情况,那么这个线程会被自动赋予偏向锁标志。这种机制的目的是为了减少锁的颗粒度开销。

偏向锁的工作原理如下:

  • 当一个线程在运行过程中一直持有锁资源时,JVM会自动将偏向锁机制升级为轻量级锁机制。
  • 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁标志,将锁恢复到标准的轻量级锁。

这种机制能够显著减少锁的颗粒度开销,但需要注意的是,偏向锁并不适用于所有情况,特别是在存在多线程争用时,偏向锁会被自动回调为轻量级锁。


2. 轻量级锁机制

轻量级锁(Lightweight Lock)是偏向锁机制升级而来的。偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的情况下,偏向锁就会被自动升级为轻量级锁。

轻量级锁的特点:

  • 轻量级锁的获取和释放操作具有非常低的开销。
  • 轻量级锁的颗粒度比偏向锁更大,但仍然比重量级锁的颗粒度要小。

轻量级锁的主要应用场景是:当一个线程在运行过程中,能够独占地持有锁资源,并且不会有其他线程进行锁争用时,轻量级锁能够提供更好的性能表现。


3. 重量级锁

重量级锁(Weighted Lock)是一种传统的同步机制。在这种锁机制下,等待锁的线程会被阻塞( Blocked)。由于这种锁机制的开销非常高,现代语言中很少使用这种锁。

Java中的synchronized关键字就是基于重量级锁机制的传统实现。重量级锁的工作原理如下:

  • 当一个线程请求锁资源时,如果锁资源已经被另一个线程持有,则该线程会被阻塞,进入等待状态。
  • 当持有锁资源的线程释放锁时,等待的线程会被唤醒,继续竞争锁资源。

重量级锁的主要缺点是:锁的颗粒度大,资源竞争时的等待时间较长,导致系统性能表现较差。


4. 重入锁机制

重入锁(Reentrant Lock)是一种递归无阻塞的同步机制。重入锁的特点是:同一线程在外层函数获得锁之后,内层递归函数仍然可以获得锁资源。这种机制能够避免死锁问题。

Java中的ReentrantLock和标准的synchronized机制都支持重入锁机制。重入锁的核心思想是:通过维护一个计数器来管理锁的使用状态。每当一个线程获取锁资源时,计数器会被加1;当线程释放锁资源时,计数器会被减1。当计数器为0时,锁资源会被释放。

重入锁的优势:

  • 支持多层级递归调用。
  • 能够避免死锁问题。
  • 允许同一线程多次获取锁资源。

5. 自旋锁机制

自旋锁(Spin Lock)是一种优化的锁机制。其核心思想是:如果一个线程发现锁资源已经被另一个线程持有,它不会直接进入等待状态,而是会进入一个忙循环(自旋),不断尝试获取锁资源。

自旋锁的优点:

  • 在锁资源被持有时间较短的情况下,自旋锁能够显著减少线程切换的开销。
  • 自旋锁的效率远高于传统的重量级锁。

自旋锁的缺点:

  • 自旋锁会占用处理器资源,尤其是在锁资源被持有时间较长的情况下,自旋锁的线程会白白浪费处理器资源。

自旋锁的主要应用场景是:当锁资源被持有时间非常短(如单个原子操作)的情况下,使用自旋锁能够提供更好的性能表现。


6. 共享锁与排他锁

在多线程环境下,共享锁(Shared Lock)和排他锁(Exclusive Lock)是两种不同的锁类型。

  • 排他锁:在同一时刻,只允许一个线程进行访问。排他锁的特点是:严格的互斥性,同一时刻只能有一个线程持有锁资源。
  • 共享锁:在同一时刻,允许多个读线程同时进行读操作。共享锁的特点是:同一时刻,多个线程可以持有锁资源,但写操作会被阻塞。

排他锁和共享锁的主要区别在于:排他锁的颗粒度更小,适用于读写分离的场景,而共享锁的颗粒度更大,适用于只允许读操作的场景。


7. 读写锁机制

读写锁(Read-Write Lock)是一种结合了共享锁和排他锁特性的锁机制。读写锁分为两种类型:读锁(Read Lock)和写锁(Write Lock)。

  • 读锁:是一个共享锁,允许多个线程同时持有锁资源,但不会阻塞其他线程。
  • 写锁:是一个排他锁,只允许一个线程持有锁资源,其他线程会被阻塞。

读写锁的特点是:读锁和写锁之间是互斥的,写锁和写锁之间也是互斥的,但读锁和读锁之间是共享的。读写锁的主要应用场景是:在多线程环境下,允许多个线程同时读取数据,而只允许一个线程进行写操作。

Java中的ReadWriteLock接口和ReentrantReadWriteLock实现类都支持读写锁机制。


8. 公平锁与非公平锁

公平锁(Fair Lock)和非公平锁(Unfair Lock)是两种不同的锁类型,主要区别在于锁的获取机制。

  • 公平锁:采用FIFO(先进先出的)机制,等待队列中的线程按照请求的时间顺序依次获取锁资源。
  • 非公平锁:采用随机机制,等待队列中的线程随机抢占锁资源。

公平锁的优点是:锁的获取是公平的,线程的等待时间会按照请求的时间顺序进行。但是,公平锁的实现机制会增加额外的开销。

非公平锁的优点是:无需维护等待队列,锁的获取更加高效。但是,非公平锁的锁的获取是随机的,可能会导致公平性的问题。

Java中的ReentrantLock支持公平锁和非公平锁的设置,默认为非公平锁。


9. 死锁与活锁

  • 死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象。死锁的发生需要满足四个条件:互斥性、等待性、占用性和无外力性。
  • 活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源;线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。最终,两个线程都无法使用资源。

活锁的问题在于:虽然每个线程都非常礼让,但由于资源的有限性,活锁现象经常会发生。活锁的解决方案是:重新设计资源的使用逻辑,确保资源能够被高效利用。


通过以上对Java多线程锁机制的全面解析,我们可以更好地理解不同锁类型的特点及其适用场景。选择合适的锁机制,能够显著提升多线程程序的性能表现。

转载地址:http://rtpmz.baihongyu.com/

你可能感兴趣的文章
Objective-C实现perfect cube完全立方数算法(附完整源码)
查看>>
Objective-C实现perfect number完全数算法(附完整源码)
查看>>
Objective-C实现perfect square完全平方数算法(附完整源码)
查看>>
Objective-C实现permutate Without Repetitions无重复排列算法(附完整源码)
查看>>
Objective-C实现pigeon sort鸽巢算法(附完整源码)
查看>>
Objective-C实现PNG图片格式转换BMP图片格式(附完整源码)
查看>>
Objective-C实现pollard rho大数分解算法(附完整源码)
查看>>
Objective-C实现Polynomials多项式算法 (附完整源码)
查看>>
Objective-C实现pooling functions池化函数算法(附完整源码)
查看>>
Objective-C实现porta密码算法(附完整源码)
查看>>
Objective-C实现Pow Logarithmic幂函数与对数函数算法 (附完整源码)
查看>>
Objective-C实现power iteration幂迭代算法(附完整源码)
查看>>
Objective-C实现powLinear函数和powFaster函数算法 (附完整源码)
查看>>
Objective-C实现pow函数功能(附完整源码)
查看>>
Objective-C实现prefix conversions string前缀转换字符串算法(附完整源码)
查看>>
Objective-C实现prefix conversions前缀转换算法(附完整源码)
查看>>
Objective-C实现pressure conversions压力转换算法(附完整源码)
查看>>
Objective-C实现Prim 算法生成图的最小生成树MST算法(附完整源码)
查看>>
Objective-C实现prime sieve eratosthenes埃拉托斯特尼素数筛选法算法(附完整源码)
查看>>
Objective-C实现PrimeCheck函数算法 (附完整源码)
查看>>