Java“锁”事

本文详细介绍了Java中的锁类型,如乐观锁与悲观锁的区别,自旋锁与挂起等待锁的使用场景,以及synchronized的关键特性,包括锁升级、消除和粗化。重点讨论了锁在多线程环境中的应用和优化策略。
摘要由CSDN通过智能技术生成

📄前言:在Java中存在种类丰富、作用各不相同的锁,本文是个人学习过程中对锁相关知识的讨论和总结。

一. 锁的分类

乐观锁 VS 悲观锁

乐观和悲观对应处理线程同步问题时,这两种锁的不同策略。

对于乐观锁而言,它认为当自己修改一个共享数据时别的线程不会同时修改,因此不会进行真正的加锁操作;当修改完成后同步内存数据时,判断该数据是否已经被其他线程修改,如果没有则同步数据,否则根据实现方式的不同进行对应的处理操作。

悲观锁在修改共享数据时持悲观态度,它认为其他线程可能在它修改的过程也修改该数据,因此在获取数据的时候就进行了加锁操作,以确保在数据不会被其他的线程修改。

由乐观锁和悲观锁的介绍可知:乐观锁适合多个线程对同一数据频繁读操作的场景,悲观锁适合对同一数据频繁写操作的场景。

自旋锁 VS 挂起等待锁

自旋锁是指一个线程尝试获取某把锁时,若该锁已被其他线程占用则采用循环的方式不断尝试获取,直到这把锁被释放后立即进行加锁操作。

对于挂起等待锁而言,当要获取的锁被占用时,线程直接调用操作系统的API进行阻塞等待状态,直到锁被释放后被唤醒,重新尝试获取锁。

由于自旋锁是利用循环的方式判断该锁是否被释放,循环判断的速度非常快,所以当锁被释放后该线程能够迅速获取该锁,避免了线程切换和恢复现场的开销,从而提高程序的响应速度;但自旋锁的缺点也很明显,若上一个加锁的线程占用锁的时间较长,自旋的线程在这个期间会白白消耗大量的CPU资源。

自旋锁的实现原理是CAS全称为Compare And Swap(比较与交换),是一种无锁算法。实现的具体流程如下:

在这里插入图片描述

由自旋锁和挂起等待锁的介绍可知:当同步资源的时间较短时,使用自旋的方式让线程处于“忙等”状态,可以节省线程切换带来的系统开销;若线程每次占用锁的时间比较长,则应采用挂起等待的方式,让出忙等”期间消耗的CUP资源。

偏向锁

偏向锁并不会真的“加锁”,只是在对象头中做一个“偏向锁的标记”,记录当前锁属于哪个线程,如果后续没有其他的线程来竞争该锁,则不会进行真正的加锁操作,从而降低了“加锁解锁”带来的系统开销。若涉及到与其他线程的竞争,会取消偏向锁状态,进入轻量级锁状态。

轻量级锁 VS 重量级锁

轻量级锁是指当形成锁竞争时,由偏向锁转化的状态。若当前只有一个线程参与锁竞争,则线程进行自旋等待;若自旋等待的线程自旋超过一定次数 或 此时又有另外的线程参与锁竞争,则轻量级锁被转变为重量级锁。

重量级锁是指当某个线程持有锁后,其他竞争该锁的线程都会进入阻塞等待的状态。

可重入锁 VS 不可重入锁

可重入锁是指某个线程持有锁后,在未释放锁之前允许多次获取同一把锁,且不会进入阻塞的状态。原因是可重入锁的内部包含了线程持有者和计数器两个信息,对于同一线程对同一把锁的重复加锁操作,计数器会进行自增,直到计数器的值为0时才释放锁。

不可重入锁是指持有锁的线程再次尝试获取该锁,会进入阻塞等待的状态,从而造成“死锁”的现象。

公平锁 VS 非公平锁

公平锁是指某把锁被释放后,竞争该锁的线程遵循“先来后到”(FIFO)的规则进行加锁。

对非公平锁而言,当多线程竞争同一把被释放的锁,采取同时竞争的方式,哪个线程都有加锁成功的可能,但非公平锁可能造成“线程饿死”的情况,即先来的线程一直没有得到加锁的机会。

读写锁

在多线程读取操作中,读操作之间不会造成多线程安全问题,写操作之间 或 写操作和读操作会产生线程安全的问题。

读写锁将读操作和写操作进行了区分,进行不同的加锁策略。即:

  • 读加锁和读加锁不互斥
  • 读加锁和写加锁互斥
  • 写加锁和写加锁互斥

若一个程序中对数据读操作较为频繁,操作间不产生互斥可以大大提高程序的性能;如果操作之间互斥,则线程会进行阻塞等待的状态。

二. synchronized

在Java中,可以使用 synchronized关键字包含代码块 或 修饰方法进行加锁操作,以达到保证线程安全的目的。有关 synchronized 的用法可以参照我之前的博客 synchronized 和 volatile——你必须知道的妙用!

1. synchronized的特性

对于以上锁的分类,synchronized 有以下特性:

  1. 一开始是乐观锁,如果锁竞争较频繁,则转变为悲观锁。(自适应)
  2. 一开始是自旋锁实现,如果锁冲突激烈,则转变为挂起等待的策略。(自适应)
  3. 一开始是轻量级锁,对应着自旋锁策略;当锁冲突激烈时转变为重量级锁,对应挂起等待的策略。(自适应)
  4. synchronized 是可重入锁。
  5. synchronized 是非公平锁。
  6. synchronized 不是读写锁。

2. synchronized的几个重要机制

锁升级

在这里插入图片描述

第一个尝试获取锁的线程,优先进入偏向锁状态;直到有另一线程尝试获取锁,synchronized 锁会升级为轻量级锁;若锁竞争较为频繁,synchronized锁最终会升级为重量级锁。
注意:synchronized 锁升级的过程的单向的。

锁消除

使用 synchronized 对某个代码块或方法加锁时,synchronized 会先判断当前环境是否存在锁竞争的可能,若判断当前程序为单线程运行或发生锁冲突的概率十分小,可能不会进行加锁操作,即锁消除

锁粗化

关于锁的粒度:当一个锁包含的代码块越多时,则称锁的粒度越粗;当代码块越少时,则称锁的粒度越细。

在实际的加锁操作中,使用细粒度的锁往往是希望某个线程获得锁后,其他的线程可以尽快地在锁释放后使用该锁;若其他线程长时间没有竞争这把锁,而细粒度锁更频繁的“加锁解锁”会带来更多的系统开销,因此 synchronized 会将“锁粗化”,即将锁的作用范围扩大,从而降低系统的开销。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值