【Java面试】十八、并发篇(中)

news2025/2/26 6:15:39

文章目录

  • 1、什么是AQS
  • 2、ReentrantLock的实现原理
    • 2.1 原理
    • 2.2 其他补充点
  • 3、synchronized和Lock有什么区别
    • 3.1 区别
    • 3.2 Demo代码
    • 3.3 signal方法的底层实现
  • 4、死锁的产生与排查
    • 4.1 死锁产生的条件是什么
    • 4.2 死锁的排查

1、什么是AQS

  • AQS,抽象队列同步器,包含三个核心抽象类,是多线程中的队列同步器,是一种锁机制,是JUC的基石,它做为一个基础框架使用,像ReentrantLock、信号量Semaphore、计时器CountDownLatch 都是基于AQS实现的
  • AQS内部维护了一个先进先出的双向队列,队列中存储的排队的线程(把排队的线程封装成一个个Node对象存FIFO队列,像银行办业务,没轮到办理的人去椅子上排排坐)
  • 在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0,代表无锁状态,如果队列中的有一个线程成功修改了state值为1,则当前线程就相等于获取了资源,即抢锁成功(银行指示灯亮了,表示有人开始办理业务了)
  • 多个线程同时去抢着修改state的情况下,有CAS保证操作的原子性

在这里插入图片描述

最后,基于AQS写出来的是公平锁还是非公平锁,取决于:新来的线程是和FIFO队列已经排队的线程一起参与CAS抢着改state(非公平锁),还是说新来的线程直接排到FIFO的队尾,让之前排队的FIFO里的队首Node里的线程先改state(公平锁)

在这里插入图片描述

2、ReentrantLock的实现原理

2.1 原理

答案:

  • ReentrantLock,可以重新进入的锁,调用lock方法获取了锁之后,本线程可以再次调用lock获取到锁,而不会再阻塞
  • ReentrantLock主要利用 CAS+AQS 来实现支持公平锁和非公平锁,在提供的构造器的中无参默认是非公平锁,也可以传参设置为公平锁
  • 可重入的实现,底层就是Monitor里的__count属性
    在这里插入图片描述

详细:

以非公平锁为例:

在这里插入图片描述

非公平锁对象NonfairSync继承自内部类Sync,Sync类又继承了AQS的类:
在这里插入图片描述

  • 线程来抢锁后使用 CAS 的方式修改 state 状态,修改状态成功为1,则让 exclusiveOwnerThread属性指向当前线程,表示获取锁成功

  • 假如修改状态失败,则会进入双向队列中等待,head 指向双向队列头部,tail 指向双向队列尾部

  • 当 exclusiveOwnerThread 为 null 的时候,则会唤醒在双向队列中等待的线程

  • 公平锁体现在新来的线程要排队才能参与抢锁,非公平锁则是新来的线程可以和FIFO队列里的head节点的线程一起抢锁

在这里插入图片描述

2.2 其他补充点

ReentrantLock,可重入锁,相比synchronized,它支持但synchronized不支持的点有:

  • 可中断:一个正在等待获取锁的线程可以被中断
  • 可以设置超时时间:超市没拿到锁可以放弃等待,synchronized则线程一直阻塞
  • 支持公平锁和非公平锁,synchronized仅非公平
  • 支持多个条件变量Condition

但ReentrantLock和synchronized都支持重入

3、synchronized和Lock有什么区别

3.1 区别

语法层面:

  • synchronized是关键字,源码在JVM,c++实现
  • Lock是接口,源码属于JDK,Java实现
  • 用synchronized,退出同步代码块时会自动释放锁,用Lock,需手动调用unlock释放锁

功能层面:

  • 二者均属于悲观锁,且都是可重入锁
  • Lock提供了很多synchronized不具备的功能,如公平锁、可打断、可超时、Condition条件
  • Lock有适合不同场景的实现,如ReentrantLock、ReentrantReadWriteLock

性能层面:

  • 竞争不激烈时,用synchronized挺好,因为做了偏向锁、轻量级锁的缓冲,不会一下到重锁进行内核态和用户态的频繁切换
  • 在竞争激烈时,Lock的实现通常会提供更好的性能

3.2 Demo代码

针对Lock的可打断:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

    //创建锁对象
    static ReentrantLock lock = new ReentrantLock();
   
    public static void main(String[] args) throws InterruptedException {

        //可打断
        lockInterrupt();
    }


    /**
     * 可打断
     * @throws InterruptedException
     */
    public static void lockInterrupt() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                //开启可中断的锁
                //lock.lock()获取到的锁阻塞时不可打断
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("等待的过程中被打断");
                return;
            }
            try {
                System.out.println(Thread.currentThread().getName() + ",获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println("主线程获得了锁");
        t1.start();

        try {
            Thread.sleep(1000);
            t1.interrupt();
            System.out.println("执行打断");
        } finally {
            lock.unlock();
        }
    }



}

main线程抢到了锁,t1线程在等待锁的时候,被main线程打断阻塞,抛出InterruptedException异常后被捕获:

在这里插入图片描述
关于Lock的可超时:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

    //创建锁对象
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        //可超时
        timeOutLock();

    }

    /**
     * 锁超时
     * @throws InterruptedException
     */
    public static void timeOutLock() throws InterruptedException {

        Thread t1 = new Thread(() -> {
            //尝试获取锁,如果获取锁成功,返回true,否则返回false
            try {
                if (!lock.tryLock(2, TimeUnit.SECONDS)) {
                    System.out.println("t1-获取锁失败");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                System.out.println("t1线程-获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        System.out.println("主线程获得了锁");
        t1.start();
        try {
            Thread.sleep(3000);
        } finally {
            lock.unlock();
        }
    } 

}

主线程获取锁后,t1去tryLock,按设定的2s后,获取锁超时

在这里插入图片描述

关于Lock的Condition条件:多个Condition对象关联同一个Lock对象,condition.await( ),有点像Object的wait思路,不同的是,Object的notify是随机唤醒一个阻塞在Object对象锁上的线程(底层是synchronized),而Lock的Condition则可以精准唤醒被哪个Condition阻塞的对象,有点线程下又分出多个协程的味道,一个Lock下分出多个Condition对象。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

    //创建锁对象
    static ReentrantLock lock = new ReentrantLock();
    //条件1
    static Condition c1 = lock.newCondition();
    //条件2
    static Condition c2 = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
    
        //多条件变量
        conditionTest();

    }

    /**
     * 多条件变量
     */
    public static void conditionTest(){
        new Thread(() -> {
            lock.lock();
            try {
                //进入c1条件的等待
                c1.await();
                System.out.println(Thread.currentThread().getName()+",acquire lock...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }, "t1").start();
        new Thread(() -> {
            lock.lock();
            try {
                //进入c2条件的等待
                c2.await();
                System.out.println(Thread.currentThread().getName()+",acquire lock...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }, "t2").start();

        new Thread(() -> {
            lock.lock();
            try {
                //唤醒c1条件的线程
                c1.signalAll();
                //唤醒c2条件的线程
                c2.signal();
                System.out.println(Thread.currentThread().getName()+",acquire lock...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }, "t3").start();


    }

}

在这里插入图片描述

3.3 signal方法的底层实现

最后,关于condition的signal方法:

在这里插入图片描述

每个Condition对象有一个单独的等待队列,在线程1里调用 Condition c1 的 await() 方法后,线程1会释放当前持有的 ReentrantLock锁,然后进入 c1自己的等待队列。当另一个线程调用 c1.signal() 或 c1.signalAll() 方法后,在c1等待队列中的t1线程将会被移动到 ReentrantLock 的等待队列(AQS的FIFO队列)中,然后开始竞争获取锁(获取锁时,就看是公平锁还是非公平锁了)

在这里插入图片描述

从这儿也可以看出来,synchronized源码在JVM(c++),写死了,改不了,AQS下的其他锁则更加灵活。

4、死锁的产生与排查

4.1 死锁产生的条件是什么

一个线程需要同事获取多把锁时,可能发生死锁

在这里插入图片描述

t1需要先拿到A锁,再拿到B锁,然后释放B锁、A锁,t2线程需要先拿到B锁,再获取A锁,然后是让A锁、B锁。此时,t1、t2互相等待,尬住,程序无异常且一直卡着:

在这里插入图片描述

4.2 死锁的排查

  • jps:类似Linux的ps -ef
  • jstack:JVM自带堆栈跟踪工具

在这里插入图片描述
还可以用可视化的jsconsole:

在这里插入图片描述

此外,也可安装VisualVM来排查,点击线程转存:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1823798.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

汇编:内联汇编和混合编程

C/C内联汇编 C/C 内联汇编(Inline Assembly)是一种在C或C代码中嵌入汇编语言指令的方法,以便在不离开C/C环境的情况下利用汇编语言的优势进行性能优化或执行特定的硬件操作。以下是一些详细的说明和示例,展示如何在C和C代码中使用…

springboot集成swagger、knife4j

1. 集成swagger2 1.1 引入依赖 <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</vers…

Redis和Docker

Redis 和 Docker 是两种不同的技术&#xff0c;它们各自解决不同的问题&#xff0c;但有时会一起使用以提供更高效和灵活的解决方案。 Redis 是一个开源的内存数据结构存储系统&#xff0c;可以用作数据库、缓存和消息代理。它设计为解决MySQL等关系型数据库在处理大量读写访问…

Python 植物大战僵尸游戏【含Python源码 MX_012期】

简介&#xff1a; "植物大战僵尸"&#xff08;Plants vs. Zombies&#xff09;是一款由PopCap Games开发的流行塔防游戏&#xff0c;最初于2009年发布。游戏的概念是在僵尸入侵的情境下&#xff0c;玩家通过种植不同种类的植物来保护他们的房屋免受僵尸的侵袭。在游…

重生之 SpringBoot3 入门保姆级学习(19、场景整合 CentOS7 Docker 的安装)

重生之 SpringBoot3 入门保姆级学习&#xff08;19、场景整合 CentOS7 Docker 的安装&#xff09; 6、场景整合6.1 Docker 6、场景整合 6.1 Docker 官网 https://docs.docker.com/查看自己的 CentOS配置 cat /etc/os-releaseStep 1: 安装必要的一些系统工具 sudo yum insta…

react 0至1 案例

/*** 导航 Tab 的渲染和操作** 1. 渲染导航 Tab 和高亮* 2. 评论列表排序* 最热 > 喜欢数量降序* 最新 > 创建时间降序* 1.点击记录当前type* 2.通过记录type和当前list中的type 匹配*/ import ./App.scss import avatar from ./images/bozai.png import {useState} …

零基础开始学习鸿蒙开发-@State的使用以及定义

1.State组件介绍 首先定义 State为鸿蒙开发的一个状态组件&#xff0c;当它修饰的组件发生改变时&#xff0c;UI也会相应的刷新&#xff0c;简单介绍就是这样&#xff0c;下面我们用代码去体会一下。 2.定义DeliverParam类 首先定义一个模型类&#xff0c;类里面定义一个构造…

Linux系统使用Docker安装Dashy导航页结合内网穿透一键发布公网

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 简介 Dashy 是一个开源的自托管的导航页配置服务&#xff0c;具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你可以将自己常用的一些网站聚合起来放在一起&#xff0c;形成自己的导航…

nc网络收发测试-tcp客户端\TCP服务器\UDP\UDP广播

netcat&#xff08;nc&#xff09;&#xff1a; 作用&#xff1a;一个功能强大的网络工具&#xff0c;提供了简单的网络测试和网络编程功能。工作原理&#xff1a;可以用于建立TCP或UDP连接&#xff0c;并发送和接收数据。示例用法&#xff1a; 监听TCP端口&#xff1a;nc -l 1…

Python魔法方法__call__深入详解

目录 1、魔法方法__call__初探 🧙‍♂️ 1.1 什么是__call__? 1.2 基础用法演示 1.3 自定义行为与参数传递 2、实现轻量级装饰器模式 🎗️ 2.1 装饰器概念回顾 2.2 利用__call__构建装饰器 2.3 深入理解装饰器应用场景 3、类实例变身函数调用 🔮 3.1 类似函数的…

【RAG】RAG性能提升之路-RAPTOR:一种构建递归文档树的增强检索方法

背景 检索增强型语言模型&#xff08;RALMs&#xff09;在处理需要不断更新的知识和大量信息的文档时确实展现出了优势。然而&#xff0c;现有的方法在处理长篇文档时存在局限性&#xff0c;主要是因为它们通常只能检索较短的文本片段&#xff0c;这限制了对整体文档上下文的全…

蓝牙资讯|苹果iOS 18增加对AirPods Pro 2自适应音频的更多控制

苹果 iOS 18 系统将为 AirPods Pro 2 用户带来一项实用功能 —— 更精细的“自适应音频”控制。AirPods Pro 2 的“自适应音频”功能包含自适应降噪、个性化音量和对话增强等特性&#xff0c;可以根据周围环境自动调节声音和降噪效果。 当更新至最新测试版固件的 AirPods Pro 2…

24年法考报名照片千万别乱拍,否则卡审

法考报名照片每年都有很多被卡审&#x1f62d; 常见的问题是 ①照片比例不对&#xff0c;无法上传&#xff0c;人像比例要求非常严格 ②照片像素错误&#xff0c;不能直接拿大图压缩图片&#xff0c;需要做出413*626像素的法考证件照 ③照片文件偏大&#xff0c;照片要求40-100…

【LeetCode刷题】前缀和解决问题:560.和为k的子数组

【LeetCode刷题】Day 16 题目1&#xff1a;560.和为k的子数组思路分析&#xff1a;思路1&#xff1a;前缀和 哈希表 题目1&#xff1a;560.和为k的子数组 思路分析&#xff1a; 问题1&#xff1a;怎样找到数组所有子数组&#xff1f; 方式一&#xff1a;暴力枚举出来&#x…

CSS实现经典打字小游戏《生死时速》

&#x1f33b; 前言 CSS 中有这样一个模块&#xff1a;Motion Path 运动模块&#xff0c;它可以使元素按照自定义的路径进行移动。本文将为你讲解这个模块属性的使用&#xff0c;并且利用它实现我小时候电脑课经常玩的一个打字游戏&#xff1a;金山打字的《生死时速》。 &…

Kettle 传参(参数)的使用

Kettle 传参的符号是 ? 。 一、给表改名&#xff0c;并在名称后面加上日期 1、表输入获取名称参数 我这是通过SQL来获取 SELECT concat("score","_",DATE_FORMAT(sysdate(),%Y%m%d%H%i)) aa FROM dual2、执行SQL语句 使用SQL脚本组件 想要获得参数&a…

win10 3389,win10操作系统如何修改3389端口

在Windows 10操作系统中&#xff0c;3389端口是远程桌面服务&#xff08;Remote Desktop Protocol, RDP&#xff09;的默认侦听端口。出于安全考虑&#xff0c;许多用户和管理员会选择修改这个默认端口号&#xff0c;以减少潜在的攻击面。下面将详细介绍如何修改Windows 10中的…

工作随机:oracle集群下的vip intermediate,failed over处理

文章目录 前言一、问题排查二、恢复db2使用1.确认db2 vip状态2.恢复db2 的vip3.检查监听&#xff1a; 前言 在对数据库进行巡检发现&#xff0c;集群中一个节点的备份没有执行&#xff0c;未生成当天的任何日志&#xff0c;查询/var/spool/oracle 信息发现提示&#xff1a;no …

会声会影封面图怎么设置 会声会影渲染封面如何固定 会声会影视频剪辑软件制作教程

使用会声会影剪辑完成过后&#xff0c;通常我们需要给我们的视频设置封面&#xff0c;渲染封面又需要进行固定。本文将围绕会声会影封面图怎么设置和会声会影渲染封面如何固定来进行介绍。 一、会声会影封面图怎么设置 会声会影不能随意自定义设置封面&#xff0c;默认情况下…

镭速如何做到数据同步文件及文件夹的ACL属性?

数据文件同步时&#xff0c;除了要同步文件的内容&#xff0c;还要对文件的属性做同步。权限属性作为一个重要的文件属性&#xff0c;是属性同步的重中之重&#xff0c;控制着不同用户与用户组对文件和文件夹的访问权限。不同的操作系统有着自己不同的权限控制机制&#xff0c;…