Java——深入理解多线程中的锁

本文深入探讨Java多线程中的锁机制,包括悲观锁、乐观锁、自旋锁、自适应锁、偏向锁、轻量级锁、重量级锁以及公平锁与非公平锁的概念和特点。通过分析不同锁的实现和升级过程,阐述了锁在并发编程中的重要性和优化策略。
摘要由CSDN通过智能技术生成

前言

在这里插入图片描述
在Java并发编程中锁的知识是必备的,所以如果在做并发项目的话,这篇文章或多或少都会给你一些想要的,本文主要从概念思想来深入理解Java中的锁,当然如果想要看代码实现的可以在下面这篇文章中查阅
《Java锁详解——lock的实现》

1. 整体了解Java多线程中的锁

看下面这张图片我们可以大体了解锁的种类
在这里插入图片描述
接下来对各种锁进行详细说明

2. 悲观锁和乐观锁

这两种主要是看开发人员对线程安全问题的角度进行设计锁。对于同一个数据的并发操作,如果在使用数据时,一定不会有其他线程修改数据,那么就不需要加锁,只需要去判断之前有没有其他线程更新了该数据。如果没有更新该数据,当前线程将自己数据成功写入。如果已经被更新了,那么就根据不同的实现方式执行不同的操作。
在java中,"乐观锁"是不采用加锁的,一般是使用CAS算法来实现,比如原子类的递增等实现。

image.png

而悲观锁是认为在使用数据的时候,一定会有其他线程去修改数据的,因此在获取数据前都会先加锁,确保数据不会被更改。在java中实现是使用Synchronized和Lock的实现类都是悲观锁。

在这里插入图片描述

根据这两种锁的特性,我们可以得出:

乐观锁比较相信数据使用时不会被更改,所以适合用于读操作比较多的场景,写操作少,不加锁能使性能大。 而悲观锁则适合写操作比较多的场景,加锁可以确保数据正确。
悲观锁就是执行加锁操作 而乐观锁是使用CAS算法实现的。
可以看这篇文章: 并发之CAS算法的技术原理

3. 自旋锁和自适应锁

在系统中,堵塞或者要唤醒线程都需要进行更改CPU的状态,进行线程上下文切换。如果同步代码里面的内容很简单,那么状态转换消耗的时间,可能会比同步代码执行的时间还要长。

当前线程去申请锁时,如果申请失败了,我们可以让它自旋一下,避免去切换线程,这就是自旋锁。

在这里插入图片描述

我们必须要清楚,如果锁被占用时间很短,自旋的时间就很短,那性能会提高。如果锁被占用的时间很长,那么自旋的时间就会很长,而且是会占用处理器资源。所以在设计自旋等待的时间要适当,并且超过一定次数还没有获得,就挂起。自旋锁的实现原理也是用CAS的,在JDK1.4引入了使用(-XX:+UseSprinning)来开启,还引入了自适应锁。

3.1 自适应自旋锁

这是对自旋锁的进化版,自适应意味着自旋的时间还有次数不固定,会根据上一个在同一个锁上的自旋时间以及锁的拥有者的"状态"来决定。如果在同一个锁对象中,自旋等待刚刚获得锁,并且持有该锁的线程正在运行中,那么系统就会认为这次自旋也可能会再次成功,会允许自旋等待更长一些的时间。
如果对于某个锁,自旋很少成功过,那么尝试获取这个锁时,会直接省略掉,直接阻塞线程,避免浪费处理器资源。

4. 不加锁=>p偏向锁=>轻量级锁=>重量级锁

讲这几个锁之前,可以看看这篇博客《为什么Synchronized能够"锁"住对象?》

4.1 不加锁

无锁就是没有加锁操作,所有线程都能访问并修改同一线程资源,降低获取锁的代价。但同时只有一个线程能修改成功。

无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。

如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。上面我们介绍的CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。

4.2 偏向锁

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。当一个线程访问同步代码块并获取锁时,会在Mark Word里存储锁偏向的线程ID。在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的偏向锁。

在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可。

偏向锁的撤销

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

image.png
在这里插入图片描述
如图,偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

如何关闭偏向锁

偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态

4.3 轻量级锁

当对象锁是偏向锁的时候,被另外的线程访问,就会升级成轻量级锁,其他线程就会通过自旋的形式来尝试获取锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值