java多线程编程面试题总结

一些最基本的基础知识就不总结了,参考之前写的如下几篇博客,阅读顺序从上到下,依次递进。

java 多线程 多线程概述及其三种创建方式 线程的常用方法

java 线程安全问题 三种线程同步方案 线程通信(了解)

java 线程池 并发并行 线程的生命周期

java 多线程乐观锁与悲观锁

目录

  • 基础知识补充
    • 新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?
    • 在 java 中 wait 和 sleep 方法的不同?
    • 如何停止一个正在运行的线程?
  • 线程中并发锁知识补充
    • synchronized关键字的底层原理
    • synchronized的锁升级机制
    • 谈谈 JMM(Java 内存模型)
    • CAS?优缺点是什么?问题是什么?
    • volatile
    • AQS
    • ReentrantLock的实现原理
    • synchronized和Lock有什么区别 ?
    • 死锁产生的条件是什么?如何避免死锁?如何如何进行死锁诊断?
    • ConcurrentHashMap
    • 并发程序出现问题的根本原因(并发三大特性)
  • ThreadLocal详解
    • ThreadLocal-内存泄露问题

基础知识补充


新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。

为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成

public class JoinTest {public static void main(String[] args) {// 创建线程对象Thread t1 = new Thread(() -> {System.out.println("t1");}) ;Thread t2 = new Thread(() -> {try {t1.join();                          // 加入线程t1,只有t1线程执行完毕以后,再次执行该线程} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2");}) ;Thread t3 = new Thread(() -> {try {t2.join();                              // 加入线程t2,只有t2线程执行完毕以后,再次执行该线程} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t3");}) ;// 启动线程t1.start();t2.start();t3.start();}}


在 java 中 wait 和 sleep 方法的不同?

共同点

  • wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

  • 方法归属不同,sleep(long) 是 Thread 的静态方法,而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
  • 醒来时机不同,首先它们都可以被interrupt()方法打断唤醒执行。正常情况sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来,wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去。
  • 锁特性不同(重点)
    • wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
    • wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu,但你们还可以用)
    • 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃cpu,你们也用不了)


如何停止一个正在运行的线程?

有三种方式可以停止线程

  • 当run方法完成后线程终止
  • 使用stop方法强行终止(不推荐,方法已作废)
  • 使用interrupt方法中断线程

其中stop方法就是下面这样使用:

MyInterrupt2 t1 = new MyInterrupt2() ;
t1.start();
// 主线程休眠2秒
Thread.sleep(6000);
// 调用stop方法
t1.stop();

注意调用interrupt()方法并不会立即停止线程,它只是改变了线程的中断状态,具体是否能够停止线程,还需要看这个线程的具体代码实现。如果线程中没有处理中断的逻辑,那么即使调用了interrupt()方法,线程也不会停止。因此,编写多线程代码时,我们需要特别注意线程的中断处理。这就是"打断唤醒"的基本含义。

package com.itheima.basic;public class MyInterrupt3 {public static void main(String[] args) throws InterruptedException {//2.打断正常的线程Thread t2 = new Thread(()->{while(true) {Thread current = Thread.currentThread();boolean interrupted = current.isInterrupted();if(interrupted) {System.out.println("打断状态:"+interrupted);break;}}}, "t2");t2.start();Thread.sleep(500);
//        t2.interrupt();}
}


线程中并发锁知识补充

synchronized关键字的底层原理

参考回答:

  • Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】
  • 它的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象(锁)关联monitor
  • 在monitor内部有三个属性,分别是owner、entrylist、waitset
  • 其中owner是关联的获得锁的线程,并且只能关联一个线程;entrylist关联的是处于阻塞状态的线程;waitset关联的是处于Waiting状态的线程


synchronized的锁升级机制

在 Java 中,synchronized 是一种关键字,用于实现线程间的同步,保护共享资源的访问。synchronized 关键字提供了一种简单且有效的锁机制,它的锁升级机制主要包括偏向锁、轻量级锁和重量级锁。下面是每种锁的简要介绍和锁升级的机制:

  • 偏向锁(Biased Locking):
    偏向锁是为了解决大部分情况下锁竞争不激烈的情况。它的设计思想是,当一个线程访问同步块时,第一次使用偏向锁来加锁,并且将线程 ID 记录在对象头中。当下一次同一线程再次访问同步块时,无需进行同步操作,直接进入临界区执行。只有在有其他线程竞争锁的情况下,偏向锁会升级为轻量级锁。

  • 轻量级锁(Lightweight Locking):
    当有多个线程竞争同一把锁时,偏向锁就会升级为轻量级锁。轻量级锁采用 CAS 操作来尝试获取锁。如果 CAS 操作失败,说明有其他线程正在竞争锁,则会进入锁膨胀的过程。在轻量级锁失败后,锁会膨胀为重量级锁。

  • 重量级锁(Heavyweight Locking):
    重量级锁是传统的同步机制,它使用操作系统的互斥量来实现线程的同步。当锁膨胀为重量级锁时,线程会进入阻塞状态,等待操作系统调度。这种锁占用的系统资源较多,性能较低,适用于锁竞争比较激烈的情况。

Java 虚拟机会根据线程竞争情况自动选择合适的锁机制,以最大程度地提高程序的性能。通过锁升级机制,Java 虚拟机可以根据具体的应用场景来优化锁的性能,从而提高并发程序的执行效率。

谈谈 JMM(Java 内存模型)

JMM(Java Memory Model)Java内存模型,是java虚拟机规范中所定义的一种内存模型。
Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

在这里插入图片描述

特点:

  • 所有的共享变量都存储于主内存(计算机的RAM)这里所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。
  • 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。
  • 线程对变量的所有的操作(读,写)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存完成。


CAS?优缺点是什么?问题是什么?

CAS的全称是: Compare And Swap(比较再交换),它体现的一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性(优点)。
在JUC( java.util.concurrent )包下实现的很多类都用到了CAS操作

  • AbstractQueuedSynchronizer(AQS框架)
  • AtomicXXX类

基于刚才学习过的JMM内存模型进行说明

线程1与线程2都从主内存中获取变量int a = 100,同时放到各个线程的工作内存中

在这里插入图片描述

一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当旧的预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。如果CAS操作失败,通过自旋的方式等待并再次尝试,直到成功

在这里插入图片描述



CAS的缺点:CAS只能保证对一个变量的操作是原子性的,无法实现对多行代码实现原子性。


CAS的问题

1.ABA问题比如有三个线程在操作同一个数据A,线程1把主内存中数据从A改成了B,线程3又把它从B改成了A,假设线程2和线程1是一起执行的,它也是想把数据从A改成B,但是经过线程1和3的操作之后内存又变成了A,这个时候线程2再去操作就会存在问题,因为在它操作之前线程1和3都操作过了,只不过它的结果恰巧符合我要操作的事情,这是不符合原子性的。
解决的方法也很简单,对操作的每个数据去追加一个版本,这样线程2再去操作的时候发现值是一样的,但是版本不同。比如AtomicStampedReference在CAS时不但会判断原值,还会比较版本信息。

请添加图片描述

2.自旋时间过长问题

  • 可以指定CAS一共循环多少次,超过这个次数还操作失败直接失败/挂起线程。
  • 可以在一次操作失败后将操作暂存起来,需要获取结果时再将暂存的操作全部执行。


volatile

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那
么就具备了两层语义:

保证线程间的可见性

保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的
值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即
写入主存。

禁止进行指令重排序

用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写
操作越过屏障,从而达到阻止重排序的效果


下面解释一下这两个含义,首先是保证线程间的可见性,如下案例所示

package com.itheima.basic;// 可见性例子
// -Xint
public class ForeverLoop {static boolean stop = false;public static void main(String[] args) {new Thread(() -> {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}stop = true;System.out.println("modify stop to true...");}).start();foo();}static void foo() {int i = 0;while (!stop) {i++;}System.out.println("stopped... c:"+ i);}
}

当执行上述代码的时候,发现foo()方法中的循环是结束不了的,也就说读取不到
共享变量的值结束循环。主要是因为在JVM虚拟机中有一个JIT(即时编辑器)给代码做了优化。在很短的时间内,foo中while循环执行的次数太多了,当达到了一个阈值,JIT就会优化此代码,直接将!stop转为true,当把代码优化成这样子以后,及时stop 变量改变为了false 也依然停止不了循环

解决方案:
第一:在程序运行的时候加入vm参数-Xint 表示禁用即时编辑器,不推荐,得不偿失(其他程序还要使用)
第二:在修饰stop 变量的时候加上volatile ,表示当前代码禁用了即时编辑器,问题
就可以解决


然后是禁止进行指令重排序,比如如下代码:

在这里插入图片描述
在去获取上面的结果的时候,有可能会出现4种情况
情况一:先执行actor2获取结果—>0,0(正常)
情况二:先执行actor1中的第一行代码,然后执行actor2获取结果—>0,1(正常)
情况三:先执行actor1中所有代码,然后执行actor2获取结果—>1,1(正常)
情况四:先执行actor1中第二行代码,然后执行actor2获取结果—>1,0(发生了指令重排序,影响结果)

解决方案:在变量上添加volatile,禁止指令重排序,则可以解决问题

在这里插入图片描述

屏障添加的示意图:

在这里插入图片描述

写操作加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下
读操作加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上

我们上面的解决方案是把volatile加在了int y这个变量上,我们能不能把它加在
int x这个变量上呢?如果使用volatile修饰了x变量,如下所示,这样显然是不行的

在这里插入图片描述

所以,现在我们就可以总结一个volatile使用的小妙招:

  • 写变量让volatile修饰的变量的在代码最后位置
  • 读变量让volatile修饰的变量的在代码最开始位置


AQS

全称是 Abstract Queued Synchronizer 它是构建锁或者其他同步组件的基础框架

synchronizedAQS
关键字,c++ 语言实现java 语言实现
悲观锁,自动释放锁悲观锁,手动开启和关闭
锁竞争激烈都是重量级锁,性能差锁竞争激烈的情况下,提供了多种解决方案

AQS常见的实现类

  • ReentrantLock 阻塞式锁
  • Semaphore 信号量
  • CountDownLatch 倒计时锁

工作机制

  • 在AQS中维护了一个使用了volatile修饰的state属性来表示资源的状态,0表示无锁,1表示有锁
  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的WaitSet

线程0来了以后,去尝试修改state属性,如果发现state属性是0,就修改state状态为1,表示线程0抢锁成功
线程1和线程2也会先尝试修改state属性,发现state的值已经是1了,有其他线程持有锁,它们都会到FIFO队列中进行等待,
FIFO是一个双向队列,head属性表示头结点,tail表示尾结点

如果多个线程共同去抢这个资源是如何保证原子性的呢?在去修改state状态的时候,使用的cas自旋锁来保证原子性,确保只能有一个线程修改成功,修改失败的线程将会进入FIFO队列中等待

AQS是公平锁吗,还是非公平锁?

如果新的线程与队列中的线程共同来抢资源,是非公平锁
如果新的线程到队列中等待,只让队列中的head线程获取锁,是公平锁

比较典型的AQS实现类ReentrantLock,它默认就是非公平锁,新的线程与队
列中的线程共同来抢资源



ReentrantLock的实现原理

ReentrantLock翻译过来是可重入锁,相对于synchronized它多具备以下特点(面试题,与synchronized的区别):

  • 可中断
  • 可以设置超时时间
  • 可以设置公平锁
  • 支持多个条件变量
  • 与synchronized一样,都支持重入

在这里插入图片描述

ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。

工作流程

在这里插入图片描述

  • 线程来抢锁后使用cas的方式修改state状态,修改状态成功为1,则让exclusiveOwnerThread属性指向当前线程,获取锁成功
  • 假如修改状态失败,则会进入双向队列中等待,head指向双向队列头部,tail指向双向队列尾部
  • 当exclusiveOwnerThread为null的时候,则会唤醒在双向队列中等待的线程
  • 公平锁则体现在按照先后顺序获取锁,非公平体现在不在排队的线程也可以抢锁


synchronized和Lock有什么区别 ?

  • 语法层面
    • synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现
    • Lock 是接口,源码由 jdk 提供,用 java 语言实现
    • 使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁
  • 功能层面
    • 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
    • Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量(重点 需要背)
    • Lock 有适合不同场景的实现,如 ReentrantLock, ReentrantReadWriteLock
  • 性能层面
    • 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
    • 在竞争激烈时,Lock 的实现通常会提供更好的性能


死锁产生的条件是什么?如何避免死锁?如何如何进行死锁诊断?

死锁:一个线程需要同时获取多把锁,这时就容易发生死锁
例如:t1 线程获得A对象锁,接下来想获取B对象的锁
t2 线程获得B对象锁,接下来想获取A对象的锁

产生死锁的四个必要条件:
1.互斥:该资源任意一个时刻只由一个线程占用。
2.占有且等待:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
3.不可强占用:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
4.循环等待:若干线程之间形成一种头尾相接的循环等待资源关系。



如何预防死锁?破坏死锁的产生的必要条件即可:
1.破坏占有且等待∶一次性申请所有的资源。
2.破坏不可强占用︰占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
3.破坏循环等待︰靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。



当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack
第一:jps查看运行的线程

在这里插入图片描述

第二:使用jstack查看线程运行的情况,下图是截图的关键信息 运行命令: jstack -l 46032

在这里插入图片描述

其他解决工具,可视化工具

  • jconsole
    用于对jvm的内存,线程,类 的监控,是一个基于 jmx 的 GUI 性能监控工具
    打开方式:java 安装目录 bin目录下 直接启动 jconsole.exe 就行
  • VisualVM:故障处理工具
    能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈
    打开方式:java 安装目录 bin目录下 直接启动 jvisualvm.exe就行


ConcurrentHashMap

JDK1.7中concurrentHashMap

在这里插入图片描述

底层采用分段的数组+链表实现
提供了一个segment数组,在初始化ConcurrentHashMap 的时候可以指定数组的长度,默认是16,一旦初始化之后中间不可扩容
在每个segment中都可以挂一个HashEntry数组,数组里面可以存储具体的元素,HashEntry数组是可以扩容的
在HashEntry存储的数组中存储的元素,如果发生冲突,则可以挂单向链表

存储流程:
先去计算key的hash值,然后确定segment数组下标
再通过hash值确定hashEntry数组中的下标存储数据
在进行操作数据的之前,会先判断当前segment对应下标位置是否有线程进行操作,为了线程安全使用的是ReentrantLock进行加锁,如果获取锁是被会使用cas自旋锁进行尝试

JDK1.8中concurrentHashMap

在这里插入图片描述

在JDK1.8中,放弃了Segment臃肿的设计,数据结构跟HashMap的数据结构是一样的:数组+红黑树+链表
采用 CAS + Synchronized来保证并发安全进行实现

  • CAS控制数组节点的添加
  • synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不
    会产生并发的问题 , 效率得到提升

并发程序出现问题的根本原因(并发三大特性)

Java并发编程三大特性

  • 原子性 一个线程在CPU中操作不可暂停,也不可中断,要不执行完成,要不不执行
  • 可见性 让一个线程对共享变量的修改对另一个线程可见
  • 有序性 即指令重排

指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保
证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终
执行结果和代码顺序执行的结果是一致的

原子性解决方案

synchronized:同步加锁
JUC里面的lock:加锁

内存可见性解决方案

synchronized
volatile(推荐)
LOCK

有序性解决方案

volatile




ThreadLocal详解

ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享

案例:使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每个线程都在各自的 Connection 上进行数据库的操作,避免A线程关闭了B线程的连接

三个主要方法:set(value) 设置值 get() 获取值 remove() 清除值


ThreadLocal的实现原理

在ThreadLocal中有一个内部类叫做ThreadLocalMap,类似于HashMap,ThreadLocalMap中有一个属性table数组,这个是真正存储数据的位置

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述




ThreadLocal-内存泄露问题

ThreadLocalMap中使用的 key为 ThreadLocal的弱引用,而value是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key会被清理掉,而value 不会被清理掉。

这样一来,ThreadLocalMap中就会出现 key为null 的Entry。假如我们不做任何措施的话,value永远无法被GC回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用set() 、get() 、 remove()方法的时候,会清理掉key为 null 的记录。使用完ThreadLocal方法后最好手动调用removeO方法

static class Entry extends >WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v; //强引用,不会被回收}
}

四种引用类型介绍这篇文章有:一文学完JVM所有知识点!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

域风网:韩国.KR域名注册量分析

近年来,随着韩国经济实力和科技实力的不断提升,该国域名注册量呈现爆发式增长,充分展现了韩国在互联网时代的重要地位。 数据显示,截至2023年6月,韩国的顶级域名(.KR)注册量已超过360万个,较2018年同期激增近40%。这一数字不仅在全球名列前茅,也远超同期其他亚洲主要国家的域名…

客户管理大揭秘!热门盘点:5款CRM工具推荐!

“本期为大家盘点客户管理中常用的5款CRM工具&#xff1a;Zoho CRM、Salesforce、Hubspot、Freshsales、和Pipedrive。” 我们会从产品特点、适用性等方面对每款产品进行全面考量。无论您是想提高客户参与度、自动化销售流程&#xff0c;还是从数据中获得可操作的见解&#xf…

Git学习笔记之基础

本笔记是阅读《git pro》所写&#xff0c;仅供参考。 《git pro》网址https://git-scm.com/book/en/v2 git官网 https://git-scm.com/ 一、git起步 1.1、检查配置信息 git config --list查看所有的配置以及它们所在的文件 git config --list --show-origin可能有重复的变量名…

在 Linux CentOS 中安装 Docker Engine(Dockers 引擎)【图文详解】

官方文档&#xff1a;https://docs.docker.com/engine/install/centos/ 操作系统要求 如果我们要在 CentOS 中安装 Docker 引擎&#xff0c;那么 CentOS 操作系统需要是以下版本之一的&#xff0c;且是处于维护的 CentOS 版本&#xff1a; CentOS 7CentOS Stream 8CentOS Str…

RocketMQ学习笔记:高可用机制(未完成)

这是本人学习的总结&#xff0c;主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、规避策略1.1、默认规避策略故障延迟机制策略 2、消息负载均衡2.1、生产者的负债均衡2.2、消费者的负载均衡 1、规避策略 所谓的高可用机制就是RocketMQ如何让生产者尽可能地成功发送消息…

VPN基础

一、VPN概念 1、概述 &#xff08;1&#xff09;虚拟专用网络(VPN)的功能是&#xff1a;在公用网络上建立专用网络&#xff0c;进行加密通讯。在企业网络中有广泛应用。VPN网关通过对数据包的加密和数据包目标地址的转换实现远程访问。VPN可通过服务器、硬件、软件等多种方式…

【小黑送书—第十四期】>>重磅升级——《Excel函数与公式应用大全》(文末送书)

今天给大家带来AI时代系列书籍&#xff1a;《Excel 2019函数与公式应用大全》全新升级版&#xff0c;Excel Home多位微软全球MVP专家打造&#xff0c;精选Excel Home海量案例&#xff0c;披露Excel专家多年研究成果&#xff0c;让你分分钟搞定海量数据运算&#xff01; 由北京…

心理治疗聊天机器人的调查

摘要 这项调查旨在调查、分析和比较现有聊天机器人在心理治疗中的可行性和缺陷。调查指出了未来心理治疗聊天机器人所需的一系列任务。我们在公共数据库中检索了约1200篇相关文献&#xff0c;并选择了五种典型的和最先进的心理治疗聊天机器人。大多数最先进的心理治疗聊天机器人…

RabbitMQ 《简单消息》

package com.xzp.rabbitmq.simple; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.xzp.rabbitmq.util.ConnectionUtil; /** * "Hello World!" * 简单消息 * 消息发送者 - R - 发送消息&#xff08;生产者&#xff09; …

Spring+thymeleaf完成用户管理页面的增删查改功能

目录 知识点&#xff1a; 路由重定向 redirect:/*** 登录 控制层代码 接口 sql配置 页面效果 添加用户 控制层代码 接口 sql配置 页面效果 查看信息 控制层代码 接口 sql配置 页面效果 修改信息 控制层代码 接口 sql配置 页面效果 条件查询 控制层代码 …

gcc任意版本安装流程(linux)

一.更换镜像源 仅仅针对有需要换镜像需求的用户 sudo gedit /etc/apt/sources.list 用于在基于Debian的Linux发行版&#xff08;如Ubuntu&#xff09;上用来编辑软件源列表的命令。 我们更换阿里源&#xff1a; deb http://mirrors.aliyun.com/ubuntu/ focal main restricted…

【CKA模拟题】不可不知:NodePort操作全攻略!

题干 For this question, please set this context (In exam, diff cluster name) kubectl config use-context kubernetes-adminkubernetesCreate a NodePort service named app-service-cka (with below specification) to expose the nginx-app-cka deployment in the ngi…