当前位置: 首页 > news >正文

RabbitMQ官方案例学习记录

官方文档:RabbitMQ教程 — RabbitMQ (rabbitmq.com)

一、安装RabbitMQ服务

直接使用docker在服务器上安装

docker run -it -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.12-management

安装完成后,访问15672端口,默认用户名和密码都是 guest,即可进入 

二、hello world——梦开始的地方

1. 介绍

        RabbitMQ 是一个消息代理:它接受并转发消息。 你可以把它想象成一个邮局:当你把你想要邮寄的邮件放在邮箱里时, 您可以确定,邮递员最终会将邮件递送给您的收件人。 在这个类比中,RabbitMQ是一个邮政信箱,一个邮局和一个信件载体。RabbitMQ和邮局的主要区别在于它不处理纸张, 相反,它接受、存储和转发数据的二进制 blob - 消息

2. 一些术语

生产者:消息的发送方

队列queue:本质上是一个大型的消息缓冲区

消费者:消息的使用方

Channel 频道:理解为操作消息队列的 client(比如 jdbcClient、redisClient),提供了和消息队列 server 建立通信的传输方法(为了复用连接,提高传输效率)。程序通过 channel 操作 rabbitmq(收发消息)

3. 编写代码

用 Java 编写两个程序;一个发送单个消息的生产者,一个接收的使用者并将消息打印出来。

(1)消息生产者:

编码过程:

先创建连接工厂,然后通过工厂创建连接,再通过连接创建channel。通过channel来绑定队列或者交换机,再用channel来生产或者消费消息。

channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

对于这行代码,可以看到消息是根据QUEUE_NAME路由到对应的队列。

import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
public class MQProducer {//设置队列名private final static String QUEUE_NAME = "hello-zy";public static void main(String[] argv) throws Exception {//创建连接工厂ConnectionFactory factory = new ConnectionFactory();//设置连接的服务器IP和端口号factory.setHost("123.249.112.12");factory.setPort(5672);//创建一个连接和通道,这里使用 try-with-resources语句,// 因为Connection和Channel都实现了java.lang.AutoCloseable,不需要在代码中再显式的关闭他们try(Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {/*创建队列 channel.queueDeclare()用于声明一个队列queue(队列名称):指定要声明的队列的名称。durable(持久化):指定队列是否是持久化的。当 RabbitMQ 重新启动时,持久化的队列将被保留下来。如果将该参数设置为 true,则队列将被持久化;如果设置为 false,则队列不会被持久化。注意,这里指的是队列本身的持久化,而不是队列中的消息。exclusive(排他性):指定队列是否是排他的。如果将该参数设置为 true,则该队列只能被当前连接的消费者使用,并且在连接关闭时会自动删除该队列。如果设置为 false,则队列可供多个消费者使用。autoDelete(自动删除):指定队列在不再被使用时是否自动删除。如果将该参数设置为 true,则当队列不再被消费者使用时,将自动删除该队列。如果设置为 false,则队列不会自动删除。arguments(参数):指定队列的其他属性和参数,以键值对的形式提供。*/channel.queueDeclare(QUEUE_NAME, false, false, false, null);//发送消息String msg = "hello,world! RabbitMQ!";//channel.basicPublish("", QUEUE_NAME, null, msg.getBytes())// 这行代码的作用是将 msg 消息发布到默认交换器(空字符串)并使用QUEUE_NAME作为路由键,// 消息的属性设置为默认值,消息的内容为 msg.getBytes(),即将 msg 转换为字节数组后发送。channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());System.out.println(" 生产者发送消息:'" + msg + "'");}}
}

 然后运行这个生产者代码,去网页端查看:

(2)消息消费者:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class MQConsumer {//声明队列 和消息发送方保持一致private final static String QUEUE_NAME = "hello-zy";public static void main(String[] args) throws IOException, TimeoutException {ConnectionFactory factory = new ConnectionFactory();//设置rabbitMQ服务端ipfactory.setHost("123.249.112.12");//这里不用try-with-resources 因为消费方需要一致保持监听,不要关闭Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);System.out.println(" 等待接收消息,退出请按 CTRL+C");//创建了一个消费者对象并实现了 handleDelivery() 方法作为回调方法。当消费者收到消息时,将自动执行该方法。DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println(" 消费了消息:'" + message + "'");};channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });}
}

去网页端查看消息是否被消费,可以看到多了一个消费者,并且消息已经没了。 

三、工作队列 WorkQueue

        工作队列(又名:任务队列)背后的主要思想是避免立即执行资源密集型任务,必须等待它要完成。相反,我们将任务安排在以后完成。我们将任务封装为消息并将其发送到队列。正在运行的工作进程 在后台将弹出任务并最终执行工作。当您运行许多工作线程时,任务将在它们之间共享。

WorkQueue的模型跟前面第一个案例Hello,World!的模型,最明显的区别其实就是,第一个案例他只有一个消费者。我们知道RabbitMQ他的消息是阅完即焚,即消费者一旦接收,这个消息直接就从Queue中被弹出了。
而现在这个案例,他有两个消费者(画两个只是方便,他当然也可以有3个、4个),他的消息应该是通过某种算法做负载均衡送到不同的消费者,让消费者进行处理,让消息不至于处理不过来,从而导致滞留在Queue中的消息被弹出。

思路如下:
1、我们先让Publish服务每秒发布50条消息到 simple.queue,来演示消息的频繁发送。
2、在Consumer服务中定义两个消费者,来监听我们的 simple.queue队列。
3、消费者1每秒处理40条消息,消费者2每秒处理30条消息。

1. 循环调度

(1)生产者:

生成50条消息:

public class Send {//设置队列名private final static String QUEUE_NAME = "hello-zy";public static void main(String[] argv) throws Exception {//创建连接ConnectionFactory factory = new ConnectionFactory();//设置连接的服务器IP和端口号factory.setHost("123.249.112.12");factory.setPort(5672);//创建一个通道,这里使用 try-with-resources语句,// 因为Connection和Channel都实现了java.lang.AutoCloseable,不需要在代码中再显式的关闭他们try(Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {channel.queueDeclare(QUEUE_NAME, false, false, false, null);//发送消息for(int i = 1; i <= 50; i++) {String msg = "hello, I am";msg = msg + i;channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());Thread.sleep(200);System.out.println(" 生产者发送消息:'" + msg + "'");}}}
}

(2)消费者1和消费者2

public class Worker_01 {//声明队列 和消息发送方保持一致private final static String QUEUE_NAME = "hello-zy";public static void main(String[] args) throws IOException, TimeoutException {ConnectionFactory factory = new ConnectionFactory();//设置rabbitMQ服务端ipfactory.setHost("123.249.112.12");//这里不用try-with-resources 因为消费方需要一致保持监听,不要关闭Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);System.out.println(" 等待接收消息,退出请按 CTRL+C");//创建了一个消费者对象并实现了 handleDelivery() 方法作为回调方法。当消费者收到消息时,将自动执行该方法。DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println(" Worker_01消费了消息:'" + message + "'");};channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });}
}public class Worker_02 {//声明队列 和消息发送方保持一致private final static String QUEUE_NAME = "hello-zy";public static void main(String[] args) throws IOException, TimeoutException {ConnectionFactory factory = new ConnectionFactory();//设置rabbitMQ服务端ipfactory.setHost("123.249.112.12");//这里不用try-with-resources 因为消费方需要一致保持监听,不要关闭Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);System.out.println(" 等待接收消息,退出请按 CTRL+C");//创建了一个消费者对象并实现了 handleDelivery() 方法作为回调方法。当消费者收到消息时,将自动执行该方法。DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println(" Worker_02消费了消息:'" + message + "'");};channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });}
}

启动两个消费者,然后开始生产消息,控制台打印如下:

 可以看到是交替消费的:

2. 消息确认

        在上面的代码中,一旦消息传递给消费者,就会立即被删除。如果在消费过程中,消费者宕机,消息没消费成功,但是因为已经投递出去了,消息从队列删掉。就会出现:消息未消费,且丢失的问题。对于这种情况,我们希望没消费成功的消息,转交给其他的消费者消费。

        为了确保消息永远不会丢失,RabbitMQ 支持消息确认。确认由消费者告诉 RabbitMQ 已收到特定消息,并处理,RabbitMQ可以自由删除它。

        如果消费者没有发送确认消息,rabbitMQ可以知道消息没有消费成功,并将重新排队。

        在消费者返回确认消息时强制实施超时(默认为 30 分钟)。 这有助于检测从不确认交付的错误(卡住)消费者。 可以按照传递确认超时中所述增加此超时。

        默认情况下,手动消息确认处于打开状态。在上一个 示例,我们通过 autoAck=true 标志明确关闭了它们。是时候将此标志设置为 false ,来使消费者发送确认消息。

(1)消费者

生产者还是不变,生成10条消息到队列中:

创建消费者,启动手动确认:

public class Consumer {private final static String QUEUE_NAME = "hello-zy";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);// 将autoAck参数设置为false,关闭自动消息确认/** new DefaultConsumer(channel) { ... }:这是一个匿名内部类,用于定义消息处理的逻辑。* 它继承自DefaultConsumer,并覆盖了 handleDelivery 方法,以自定义消息的处理方式。* */channel.basicConsume(QUEUE_NAME, false, new DefaultConsumer(channel) {/*handleDelivery 方法:这是 DefaultConsumer 类中的方法,用于处理从队列中接收的消息。consumerTag:标识消费者的标签。envelope:包含与消息相关的元数据,如交付标签、交付模式等。properties:包含消息的属性,如消息的头部信息。body:消息的内容,以字节数组形式提供。*/@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String message = new String(body, "UTF-8");System.out.println("消费者接收消息: '" + message + "'");// 在消息处理成功后,发送确认消息channel.basicAck(envelope.getDeliveryTag(), false);System.out.println("确认收到了消息:"+message);}});}
}

启动消费者:

(2)验证未成功消费情况

先生产6条消息到队列:

简单修改一下消费者代码,在中间添加判断逻辑,当碰到消息5的时候,退出消费者:

public class Consumer1 {private final static String QUEUE_NAME = "hello-zy";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);try {// 将autoAck参数设置为false,关闭自动消息确认channel.basicConsume(QUEUE_NAME, false, new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)throws IOException {String message = new String(body, "UTF-8");System.out.println("消费者接收消息: '" + message + "'");// 模拟某个条件,例如消息处理成功if (!message.contains("5")) {// 在消息处理成功后,发送确认消息channel.basicAck(envelope.getDeliveryTag(), false);System.out.println("确认收到了消息:" + message);}// 模拟消费者退出if (message.contains("5")) {throw new RuntimeException("Consumer exiting...");}}});} catch (RuntimeException e) {// 当消费者退出时,捕获异常System.out.println("Consumer exited.");}}
}

 

可以看到,当消费者消费到第五条消息后,因为抛出了异常,所以后面的消息都未消费成功,所以会把第五条消息和第六条消息再放回消息队列,查看消息队列,可以看到还有两条消息在队列中:

如果把参数改成true,也就是自动确认:这就会导致未消费的消息丢失

 

3. 消息持久性

我们已经保证了消息未消费的情况下不会丢失,但是如果RabbitMQ服务器宕机,消息还是会丢失。这就涉及到一个消息持久化的问题。

需要做两件事来确保 消息不会丢失:我们需要将队列和消息都标记为durable。 (重新定义一个队列)

此时,我们确信durable_queue队列不会丢失 即使 RabbitMQ 重新启动。现在我们需要将我们的消息标记为持久 - 通过设置消息属性(实现基本属性) 到值PERSISTENT_TEXT_PLAIN。 

 

如果不设置未持久性,重启docker的rabbitmq容器,队列消息就丢了,设置durable=true后, 即使重启docker容器,队列和消息都不会丢失。

四、发布/订阅模式

        在前面,都是一条消息由一个消费者消费。如果一条消息需要被多个消费者消费,那么就需要引入发布/订阅模式。

1. 交换机

关于交换机的概念():

一个生产者给 多个 队列发消息,1 个生产者对多个队列。
交换机的作用:提供消息转发功能,类似于网络路由器
要解决的问题:怎么把消息转发到不同的队列上,好让消费者从不同的队列消费。

交换机有多种类别:fanout、direct, topic, headers

fanout(扇出)

扇出、广播
特点:消息会被转发到所有绑定到该交换机的队列
场景:很适用于发布订阅的场景。比如写日志,可以多个系统间共享

Direct 直接

绑定:可以让交换机和队列进行关联,可以指定让交互机把什么样的消息发送给哪个队列(类似于计算机网络中,两个路由器,或者网络设备相互连接,也可以理解为网线)
routingKey:路由键,控制消息要转发给哪个队列的(IP 地址) 

特点:消息会根据路由键转发到指定的队列
场景:特定的消息只交给特定的系统(程序)来处理
绑定关系:完全匹配字符串

比如发日志的场景,希望用独立的程序来处理不同级别的日志,比如 C1 系统处理 error 日志,C2 系统处理其他级别的日志

 

 topic 交换机

特点:消息会根据一个 模糊的 路由键转发到指定的队列
场景:特定的一类消息可以交给特定的一类系统(程序)来处理
绑定关系:可以模糊匹配多个绑定
●*:匹配一个单词,比如 *.orange,那么 a.orange、b.orange 都能匹配
●#:匹配 0 个或多个单词,比如 a.#,那么 a.a、a.b、a.a.a 都能匹配

注意,这里的匹配和 MySQL 的like 的 % 不一样,只能按照单词来匹配,每个 '.' 分隔单词,如果是 '#.',其实可以忽略,匹配 0 个词也 ok

Headers 交换机 

类似主题和直接交换机,可以根据 headers 中的内容来指定发送到哪个队列。使用消息头headers来路由消息。

 2. 使用fanout交换机来实现发布/订阅

(1)生产者

public class LogProducer {private final static String EXCHANGE_NAME = "logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {//声明交换机类型为:FANOUTchannel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);for (int i = 1; i <= 5; i++) {String message = "Log message " + i;//消息发到交换机中,而不是像之前点对点那样直接发到消息队列中channel.basicPublish(EXCHANGE_NAME, "", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());System.out.println("生产者发送日志: " + message);}}}
}

查看交换机列表,可以看到新增了一个名为logs的交换机,类型为 fanout:

 (2)消费者

创建两个消费者,都绑定一个交换机,名字为log

public class LogConsumer {private final static String EXCHANGE_NAME = "logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);// 创建一个临时队列String queueName = channel.queueDeclare().getQueue();// 将队列绑定到交换器channel.queueBind(queueName, EXCHANGE_NAME, "");System.out.println("等待接收日志消息...");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println("消费者1接收消息: '" + message + "'");};channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });}
}
public class LogConsumer2 {private final static String EXCHANGE_NAME = "logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);// 创建一个临时队列String queueName = channel.queueDeclare().getQueue();// 将队列绑定到交换器channel.queueBind(queueName, EXCHANGE_NAME, "");System.out.println("等待接收日志消息...");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println("消费者2接收消息: '" + message + "'");};channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});}
}

 启动两个消费者,然后启动生产者发送消息,可以看到两个消费者都消费了消息:

五、路由

在上面的例子中,我们实现了一个简单的日志记录系统。能够向多个消费者广播消息。

下面要增加一个新的功能:

        例如,我们将只能将关键错误消息定向到 日志文件(以节省磁盘空间),同时仍然能够打印所有控制台上的日志消息。

1. 绑定队列和交换机

之前是这样绑定的:第三个参数就是路由键

channel.queueBind(queueName, EXCHANGE_NAME, "");

绑定是交换和队列之间的关系。

        绑定可以采用额外的路由键参数。为了避免 与basic_publish参数混淆,我们将它称为绑定键。绑定键的含义取决于交换类型。

channel.queueBind(queueName, EXCHANGE_NAME, "black");

2. 使用direct交换机绑定

(1)生产者

模拟三条不同的消息,指定消息1的路由键为orange,消息2的路由键为black,消息3的路由键为green。

public class LogProducer {private final static String EXCHANGE_NAME = "direct_logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);try (Connection connection = factory.newConnection();Channel channel = connection.createChannel()) {//声明交换机类型为 directchannel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);String message1 = "Message with routing key orange";String message2 = "Message with routing key black";String message3 = "Message with routing key green";// 发布消息到交换器,并指定不同的路由键channel.basicPublish(EXCHANGE_NAME, "orange", null, message1.getBytes());channel.basicPublish(EXCHANGE_NAME, "black", null, message2.getBytes());channel.basicPublish(EXCHANGE_NAME, "green", null, message3.getBytes());System.out.println("生产者发送消息完成.");}}
}

(2)消费者

创建两个消费者

消费者1和消费者2都绑定对应的交换机,其中消费者1对应路由键orange,消费者2对应路由键black和orange。

public class LogConsumer1 {private final static String EXCHANGE_NAME = "direct_logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);// 创建一个临时队列String queueName = channel.queueDeclare().getQueue();// 绑定队列到交换器,指定路由键为 "orange"channel.queueBind(queueName, EXCHANGE_NAME, "orange");System.out.println("等待接收 orange 消息...");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println("消费者1接收消息: '" + message + "'");};channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});}
}
public class LogConsumer2 {private final static String EXCHANGE_NAME = "direct_logs";public static void main(String[] argv) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("123.249.112.12");factory.setPort(5672);Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);// 创建一个临时队列String queueName = channel.queueDeclare().getQueue();// 绑定队列到交换器,指定路由键为 "black" 和 "green"channel.queueBind(queueName, EXCHANGE_NAME, "black");channel.queueBind(queueName, EXCHANGE_NAME, "green");System.out.println("等待接收 black 和 green 消息...");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");System.out.println("消费者2接收消息: '" + message + "'");};channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});}
}

相关文章:

RabbitMQ官方案例学习记录

官方文档&#xff1a;RabbitMQ教程 — RabbitMQ (rabbitmq.com) 一、安装RabbitMQ服务 直接使用docker在服务器上安装 docker run -it -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.12-management 安装完成后&#xff0c;访问15672端口&#xff0c;默认用户…...

Ikigai: 享受生命的意义

你为什么而活&#xff1f;你的存在意义是什么&#xff1f;除了工作、挣钱&#xff0c;还有什么值得投入人生&#xff1f;Ikigai是来自日本的哲学思想&#xff0c;意味着带给你快乐、让你享受生活的东西。知道自己的Ikigai&#xff0c;也许人生可以减少很多焦虑、痛苦和烦恼。世…...

ios 实现PDF,Word,Excel等文档类型的读取与预览

文章目录 一、前言二、iCould相关配置三、功能实现3.1 UIDocumentPickerViewController 选取控制器3.2 读取文件3.3 文档预览3.3.1 下载并保存3.3.2 QLPreviewController预览文档四、总结一、前言 最近正在研发的项目有一个需求: 允许用户将iCloud中的文档上传,实现文件的流…...

速锐得解码匹配特斯拉电动汽车安全性能检测车架号及BMS电池数据

电动汽车三大件分别是电池、电机和电控。到目前为止&#xff0c;电机技术已经非常成熟&#xff0c;直流永磁电机、永磁同步电机已经取代了异步电机&#xff0c;成为电动汽车的主流。很多人认为电动汽车最后一道技术门槛是电池&#xff0c;但在我国&#xff0c;汽车制造商在制造…...

mysql视图中转换表字段的数据类型

需求: 开发框架中需要视图c_smelt_group字段类型是字符串的&#xff0c;而数据库表中c_smelt_group字段是int类型。 解决办法: wt是表的别名&#xff0c;通过cast函数&#xff0c;将wt.c_smelt_group字段转成字符串类型&#xff0c; AS c_smelt_group 是给字段取别名。 cast( …...

深入理解Java中的Synchronized关键字

文章目录 &#x1f4dd; 定义&#x1f4dd; JDK6以前&#x1f525; 对象从无锁到偏向锁转化的过程&#x1f525; 轻量级锁升级&#x1f525; 自旋锁&#x1f525; 重量级锁 &#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专…...

力扣每日一题58:最后一个单词的长度

题目描述&#xff1a; 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1&#xff1a; 输入&#xff1a;s "Hello World&q…...

mybatis书写

mybatis <select id"selectUserList" resultType"map"> select * from user </select><!--根据主键查询一条--> <select id"selectById" resultType"map" parameterType"java.lang.Integer"> sele…...

Win32 命名管道

命名管道简单封装 CNamedPipe.h #pragma once #include <string> #include <windows.h> #include <tchar.h>#pragma warning(disable:4200)class CNamedPipe { public:CNamedPipe();~CNamedPipe();CNamedPipe(const CNamedPipe& r) delete;CNamedPipe&…...

Flutter 填坑录 (不定时更新)

一&#xff0c;内存爆表 > 图片缓存 /// State基类 class BaseState<T extends StatefulWidget> extends State<T>withAutomaticKeepAliveClientMixin,WidgetHelper,DialogHelper,EventListener {mustCallSupervoid initState() {if (isListenEvent()) {EventMa…...

如何提高webpack的构建速度?

一、背景 随着我们的项目涉及到页面越来越多&#xff0c;功能和业务代码也会随着越多&#xff0c;相应的 webpack 的构建时间也会越来越久 构建时间与我们日常开发效率密切相关&#xff0c;当我们本地开发启动 devServer 或者 build 的时候&#xff0c;如果时间过长&#xff…...

Linux:实用操作

Linux&#xff1a;实用操作 1. 各类小技巧1.1 controlc(ctrl c) 强制停止1.2 可以通过快捷键&#xff1a;control d(ctrl d)&#xff0c;退出账户的登录1.3 历史命令搜索1.4 光标移动快捷键 2. 软件安装2.1 介绍2.2 yum命令(需要root权限)在这里插入图片描述 3. systemctl4.…...

【Linxu工具】:vim使用及简单配置

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux工具&#xff1a;vim的使用&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从…...

众和策略:题材股什么意思?

题材股是股票商场上的一个术语&#xff0c;许多刚接触股票出资的人可能对它不太熟悉。那么&#xff0c;题材股什么意思呢&#xff1f;在本文中&#xff0c;咱们将从多个角度剖析这个问题&#xff0c;帮忙读者更好地了解。 一、什么是题材股 题材股是指某个工作或主题的股票集结…...

npm 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

一、报错&#xff1a; npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c; 然后再试一次。 所在位置 行:1 字符: 1npm init -y~~~ CategoryInfo : ObjectNotFo…...

港联证券:短债基金收益?

跟着人们对理财的需求不断增加&#xff0c;短债基金成为了许多出资者关注的焦点。那么&#xff0c;短债基金可以带来什么样的收益呢&#xff1f;本文将从多个角度剖析短债基金的收益。 一、短债基金的概念 短债基金是一种基金类型&#xff0c;风险相对较低&#xff0c;一般出资…...

每日一题 2316. 统计无向图中无法互相到达点对数(中等,图连通分量)

题目很简单&#xff0c;只要求出每个连通分量有多少个节点即可首先通过建立一个字典来表示每个节点的邻接关系遍历每个节点&#xff0c;并通过邻接关系标记在当前连通分量内的所有的点&#xff0c;这样就可以知道一个连通分量内有多少个点在这里我陷入了一个误区&#xff0c;导…...

Centos 无法连接 WIFI

环境 硬件&#xff1a;ASUS X550VC, x86_64系统&#xff1a;CentOS 7.9 现象 系统安装后无法上网&#xff0c;终端命令提示符为shadow3dlocalhost&#xff0c;我的疑问是这里不是应该显示我的主机名吗&#xff0c;为什么是localhost呢&#xff1f;但是有些时候&#xff0c;又…...

whois人员信息python批处理读入与文本输出

使用pytho读取一个ip列表文本&#xff0c;批量获取whois输出并写入到一个文本 import socketif __name__ __main__:# 江苏电信DNS地址mylog open(whois.log, mode a,encodingutf-8)for line in open("ip.txt"):s socket.socket(socket.AF_INET, socket.SOCK_STR…...

阿里云服务器续费流程_一篇文章搞定

阿里云服务器如何续费&#xff1f;续费流程来了&#xff0c;在云服务器ECS管理控制台选择续费实例、续费时长和续费优惠券&#xff0c;然后提交订单&#xff0c;分分钟即可完成阿里云服务器续费流程&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云服务器详细续费方法&am…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行

项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战&#xff0c;克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

【网络安全】开源系统getshell漏洞挖掘

审计过程&#xff1a; 在入口文件admin/index.php中&#xff1a; 用户可以通过m,c,a等参数控制加载的文件和方法&#xff0c;在app/system/entrance.php中存在重点代码&#xff1a; 当M_TYPE system并且M_MODULE include时&#xff0c;会设置常量PATH_OWN_FILE为PATH_APP.M_T…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁

赛门铁克威胁猎手团队最新报告披露&#xff0c;数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据&#xff0c;严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能&#xff0c;但SEMR…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物&#xff0c;因为每个访问一个线程局部变量的线程&#xff08;通过其 get 或 set 方法&#xff09;都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段&#xff0c;这些类希望将…...