故事

地铁上,小帅双目空洞地望着窗外...绝望,发自内心地感到绝望...

距离失业已经过去两个月了,这是小帅接到的第四次面试邀请。“回去等通知吧...”,简简单单的六个字,把小帅的心再次打入了冰窖。

上次“【ThreadLocal问出花】”,小帅其实也有吸取教训得,这次对于多线程的问题还是做了很多准备的...可是没想到这次的结果居然也还是这样。

“Java中的锁了解吧?介绍一下吧”,面试官不紧不慢地问到。

“乐观锁、悲观锁、公平锁、非公平锁,然后平时咱们的synchronized是基于.....”小帅把知道的所有关于锁的基本都回答了一遍。

面试官对他笑了笑,“就这些吗?还有呢?比如自旋锁、可重入锁、独占锁....并且说一下你的理解,或者聊一下使用场景的优劣吧。”

“额.....以前好像看到过...”小帅语无伦次地回答到。

“嗯,行吧,之前的那些答得可以的,不过一会我这边有个会,要不今天咱们就聊到这里?回去等通知吧...”

Java中让人眼花缭乱的锁你是否真的一一清楚了?

试问这样一个大而宽的问题,大家能够总结全吗,如果让各位来回答,能否回答完全呢?

我们在实际的并发编程中,常常遇到多个线程访问一个共享变量的情况,当同时对共享变量进行读写操作的时候,就会产生数据不一致的情况。为了保证资源获取的有序性,我们就常常会用到并发锁。

那么接下来咱们就来聊聊这些Java并发锁的理解吧。我们将从以下这些方面来一起回顾一下Java中的并发锁。

乐观锁和悲观锁:线程是否锁住同步资源

大家其实对乐观锁和悲观锁听说的比较多一些,所以咱们就先来聊聊这两种类型的锁。这两种类型的锁,本质区分是要看线程是否锁住同步资源。

先来看一下悲观锁。悲观锁就是每次去拿数据的时候都会认为别人会修改数据,所以在读取数据的时候都会上锁。这样就会导致线程临时阻塞。

再来看一下乐观锁,乐观锁就是每次在拿数据的时候都假设别人不会修改数据,所以都不会进行上锁;只有在更新数据的时候才去判断之前有没有别的线程更新了这条数据。如果没有更新,那么当前线程会自己修改数据并且写入成功。如果数据已经被其他线程更新了,那么会报错或者自动重试,例如下图。

上述两种锁,并没有优劣之分。只是看相关的场景然后分别去使用。

乐观锁:适用于写少读多的场景。因为不用上锁,释放锁,省去了锁的开销,从而提升了吞吐量。

悲观锁:适用于写多读少的场景。因为线程竞争激烈,如果使用乐观锁会导致线程不断进行重试,反而降低吞吐量。

共享锁和独占锁:多个线程是否共享同一把锁

并发场景下,如果多个线程能够共享一把锁,那么就是所谓的共享锁,如果不能,那么则为独占锁(其他命名:排他锁或者独享锁)。

共享锁指锁可以被多个线程持有。如果一个线程对数据加上共享锁,那么其他线程只能对数据再加共享锁,不能加独占锁。另外的共享锁的线程只能读数据,不能修改数据。如下图。

独占锁是指锁一次只能被一个线程持有,如果一个线程对数据加上独占锁,那么其他的线程则不能对该数据再加任何类型的锁。如果一个线程获取独占锁,那么则该线程既可以读数据又可以修改数据。

对于独占锁来说,大家比较熟悉的就是synchronized和J.U.C包中的Lock实现类。

大家可能也听说过互斥锁,其实互斥锁就是独占锁的一种常规实现。

读写锁是共享锁的一种具体实现。读写锁管理一组锁,一个是只读的锁,一个是写锁。

读锁可以再没有写锁的时候被多个线程同时持有,而写锁是独占的,于此同时写锁的优先级要高于读锁,一个获得了读锁的线程必须能看到前一个释放的写锁更新的内容。

读写锁和互斥锁对比,其性能更高,每次只有一个写线程,但是有多个线程可以并发读。

例如,ReentrantReadWriteLock。具体伪代码如下:

import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 公众号:程序员老猫
**/
public class ReadWriteLockDemo { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void readData() {
lock.readLock().lock(); // 获取读锁
try {
// 读取共享数据
} finally {
lock.readLock().unlock(); // 释放读锁
}
} public void writeData() {
lock.writeLock().lock(); // 获取写锁
try {
// 修改或写入数据
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}
}

公平锁和非公平锁:多线程竞争时是否要排队

我们根据多线程在竞争锁的时候是否需要排队从来判断其锁的类型是公平锁还是非公平锁。

公平锁指多个线程按照申请锁的顺序来获取锁。类似食堂排队打饭,先到的可以先打饭。

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序进行的,有可能后申请的比先申请的优先获得锁,高并发场景下,优先级就有可能发生反转。如下图:

咱们在日常开发的过程中经常用到synchronized,其底层其实就是非公平锁。当然如果我们要使用公平锁的情况下,我们也可以使用ReentrantLock。

伪代码如下:

Lock lock = new ReetrantLock(false);

ReentrantLock默认为非公平锁,设置为true的时候表示公平锁。当设置为false的时候表示非公平锁。

可重入锁和不可重入锁:同一个线程中多个流程是否能够获取同一把锁。

如果一个线程中的多个流程能够获取同一把锁,那么我们就叫该所为可重入锁,反之则为不可重入锁。咱们光看文字描述的话可能比较抽象。我们看一下下图。

在Java中可重入锁一般有ReentrantLock,其命名就已经很明确了。另外的synchronized也是可重入锁。

可重入锁的优势是可以一定程度上避免死锁发生。上面的示意图转换为如下demo:

public synchronized void methodA() {
methodB()
} public synchronized void methodB() {
methodC()
} public synchronized void methodC(){
doSomeThing()
}

自旋锁或者自适应自旋锁:线程锁定同步资源失败,如该线程没有被阻塞场景下发生

如果一个线程锁住同步资源失败,但是又希望这个线程不被阻塞,那么此时咱们就可以使用自旋锁或者自适应自旋锁。

自旋锁指线程没有获得锁的情况下不被挂起,而是执行一个忙循环。那么这个忙循环的话就成为自旋。如下:

目的:减少线程被挂起的概率,因为线程被挂起和唤醒也是消费资源。

Java中AtomicInteger类就有自旋的操作,如下源代码:

@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}

上述方法中weakCompareAndSetInt(),就可以被称为是CAS操作,如果失败,那么会一直循环获取当前的value值然后进行重试操作。那么这个过程其实就是自旋了。

其他分类的锁。

上述我们聊到的这系列的锁应该是大家听到比较多的。其实还有其他的分类。在此不做一一展开了,有兴趣的小伙伴当然也可以深入去了解一下。

例如根据线程竞争同步资源的时候,细节流程是否发生变化,分为偏向锁、轻量级锁和重量级锁。

在比如,相信大家对HashMap底层原理倒背如流吧,对ConcurrentHashMap应该也有了解,那么ConcurrentHashMap底层其实将锁的粒度进一步细化了,存在了分段锁的概念等等。

总结

这些让人眼花缭乱的锁,如果面试官问到的话,大家是否能够说出一二呢?相信看完上面的解释,大家心里多多少少也有数了吧。当然关于最后一点其他分类的锁,老猫没有展开。有兴趣的小伙伴可以自行查阅一下这些分类。

简直了,被“Java并发锁”问题追问到自闭...的更多相关文章

  1. 深入理解 Java 并发锁

    本文以及示例源码已归档在 javacore 一.并发锁简介 确保线程安全最常见的做法是利用锁机制(Lock.sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方 ...

  2. Java 并发锁

    Java 中的锁 阻塞锁.可重入锁.读写锁.互斥锁.悲观锁.乐观锁.公平锁.偏向锁.对象锁.线程锁.锁粗化.锁消除.轻量级锁.重量级锁.信号量.独享锁.共享锁.分段锁 一.常见的锁 synchroni ...

  3. java并发锁ReentrantReadWriteLock读写锁源码分析

    1.ReentrantReadWriterLock 基础 所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读 ...

  4. 如何理解Java中眼花缭乱的各种并发锁?

    在互联网公司面试中,很多小伙伴都被问到过关于锁的问题. 今天,我给大家一次性把Java并发锁的全家桶彻底讲明白.包括互斥锁.读写锁.重入锁.公平锁.悲观锁.自旋锁.偏向锁等等等等.视频有点长,大家一定 ...

  5. 百万并发中间件系统的内核设计看Java并发性能优化

    “ 这篇文章,给大家聊聊一个百万级并发的中间件系统的内核代码里的锁性能优化. 很多同学都对Java并发编程很感兴趣,学习了很多相关的技术和知识.比如volatile.Atomic.synchroniz ...

  6. 深入并发锁,解析Synchronized锁升级

    这篇文章分为六个部分,不同特性的锁分类,并发锁的不同设计,Synchronized中的锁升级,ReentrantLock和ReadWriteLock的应用,帮助你梳理 Java 并发锁及相关的操作. ...

  7. Java并发指南4:Java中的锁 Lock和synchronized

    Java中的锁机制及Lock类 锁的释放-获取建立的happens before 关系 锁是java并发编程中最重要的同步机制.锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消 ...

  8. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  9. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  10. 【Java并发系列04】线程锁synchronized和Lock和volatile和Condition

    img { border: solid 1px } 一.前言 多线程怎么防止竞争资源,即防止对同一资源进行并发操作,那就是使用加锁机制.这是Java并发编程中必须要理解的一个知识点.其实使用起来还是比 ...

随机推荐

  1. 实操开源版全栈测试工具RunnerGo安装(四)Windows安装

    以windows 10系统为例 视频教程:https://www.bilibili.com/video/BV14H4y1C71u/?spm_id_from=333.999.0.0 1.设置手动进入系统 ...

  2. 全栈式测试平台RunnerGo核心功能模块-接口管理

    ​全栈式测试平台RunnerGo相对于市面上其他性能测试产品来说更简单,它不用其他相关配件,天然支持分布式,有单独的机器做分布式的负载均衡,自有一套智能算法算压力机的配置从而平均分配,并从场景链路的流 ...

  3. Docker方式快速启动一个Redis实例

    安装Redis有多种方式,除了可以通过各个平台的软件包工具安装外,还可以直接从源码安装. 但是,安装Redis可能会遇到一些这样的问题,比如: 1.网络环境比较差,下载耗时比较长 2.从源码编译安装时 ...

  4. InSAR处理软件——Gamma 安装教程

    Gamma是由瑞士 GAMMA Remote Sensing 公司开发SAR数据处理软件,支持SAR数据全流程处理,是最InSAR最常用的软件.下面介绍该软件的安装流程,安装环境为Ubuntu16.0 ...

  5. Linux驱动开发笔记(一):helloworld驱动源码编写、makefile编写以及驱动编译基本流程

    前言   基于linux的驱动开发学习笔记,本篇是描述了一个字符驱动的基础开发流程,以便做嵌入式开发多年的应用或者系统学习驱动开发.   笔者自身情况   笔者拥有硬件基础,单片机软硬基础,linux ...

  6. pwd模块

    # pwd模块提供了获取UNIX平台用户的账户与密码信息(通过文件/etc/passwd),在所有的UNIX版本平台都可以用. # pwd模块返回的是一个类似元组的对象,该对象的各个属性对应于pass ...

  7. 【LeetCode栈与队列#05】滑动窗口最大值

    滑动窗口最大值 力扣题目链接(opens new window) 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧.你只可以看到在滑动窗口内的 k 个数字.滑动窗口 ...

  8. Netty笔记(7) - 使用Netty 模仿 Dubbo 实现简单的 远程调用

    使用Netty 模仿 Dubbo 实现简单的 远程调用 使用 java的反射 动态代理 加 Netty的远程访问 实现根据接口的RPC 远程调用 定义两个公共接口: public interface ...

  9. [MAUI 项目实战] 音乐播放器(二):播放内核

    播放控制服务 IMusicControlService: 播放控制类,用于当前平台播放器对象的操作,对当前所播放曲目的暂停/播放,下一首/上一首,快进快退(寻迹),随机.单曲模式等功能的控制. 播放控 ...

  10. mybatis批量插入的四种方式

    一.循环插入 public void insert(List<User> userList) { userList.forEach(user -> userDao.insert(us ...