实战:10 种实现延迟任务的方法,附代码!

a7e6f1f65094ff47a08aa85b755bb9bd.png

作者 | 磊哥

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

这篇文章的诞生要感谢一位读者,是他让这篇优秀的文章有了和大家见面的机会,重点是优秀文章,哈哈。

事情的经过是这样的...

d9fe79c4d269ae123fbf07cd416815f8.png

不用谢我,送人玫瑰,手有余香。相信接下来的内容一定不会让你失望,因为它将是目前市面上最好的关于“延迟任务”的文章,这也一直是我写作追求的目标,让我的每一篇文章都比市面上的好那么一点点。

好了,话不多说,直接进入今天的主题,本文的主要内容如下图所示:

052b2ee7b6ad4d267f3dd3d687c0cb36.png

什么是延迟任务?

顾明思议,我们把需要延迟执行的任务叫做延迟任务

延迟任务的使用场景有以下这些:

  1. 红包 24 小时未被查收,需要延迟执退还业务;

  2. 每个月账单日,需要给用户发送当月的对账单;

  3. 订单下单之后 30 分钟后,用户如果没有付钱,系统需要自动取消订单。

等事件都需要使用延迟任务。

延迟任务实现思路分析

延迟任务实现的关键是在某个时间节点执行某个任务。基于这个信息我们可以想到实现延迟任务的手段有以下两个:

  1. 自己手写一个“死循环”一直判断当前时间节点有没有要执行的任务;

  2. 借助 JDK 或者第三方提供的工具类来实现延迟任务。

而通过 JDK 实现延迟任务我们能想到的关键词是:DelayQueue、ScheduledExecutorService,而第三方提供的延迟任务执行方法就有很多了,例如:Redis、Netty、MQ 等手段。

延迟任务实现

下面我们将结合代码来讲解每种延迟任务的具体实现。

1.无限循环实现延迟任务

此方式我们需要开启一个无限循环一直扫描任务,然后使用一个 Map 集合用来存储任务和延迟执行的时间,实现代码如下:

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;/*** 延迟任务执行方法汇总*/
public class DelayTaskExample {// 存放定时任务private static Map<String, Long> _TaskMap = new HashMap<>();public static void main(String[] args) {System.out.println("程序启动时间:" + LocalDateTime.now());// 添加定时任务_TaskMap.put("task-1", Instant.now().plusSeconds(3).toEpochMilli()); // 延迟 3s// 调用无限循环实现延迟任务loopTask();}/*** 无限循环实现延迟任务*/public static void loopTask() {Long itemLong = 0L;while (true) {Iterator it = _TaskMap.entrySet().iterator();while (it.hasNext()) {Map.Entry entry = (Map.Entry) it.next();itemLong = (Long) entry.getValue();// 有任务需要执行if (Instant.now().toEpochMilli() >= itemLong) {// 延迟任务,业务逻辑执行System.out.println("执行任务:" + entry.getKey() +" ,执行时间:" + LocalDateTime.now());// 删除任务_TaskMap.remove(entry.getKey());}}}}
}

以上程序执行的结果为:

程序启动时间:2020-04-12T18:51:28.188

执行任务:task-1 ,执行时间:2020-04-12T18:51:31.189

可以看出任务延迟了 3s 钟执行了,符合我们的预期。

2.Java API 实现延迟任务

Java API 提供了两种实现延迟任务的方法:DelayQueue 和 ScheduledExecutorService。

① ScheduledExecutorService 实现延迟任务

我们可以使用 ScheduledExecutorService 来以固定的频率一直执行任务,实现代码如下:

public class DelayTaskExample {public static void main(String[] args) {System.out.println("程序启动时间:" + LocalDateTime.now());scheduledExecutorServiceTask();}/*** ScheduledExecutorService 实现固定频率一直循环执行任务*/public static void scheduledExecutorServiceTask() {ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);executor.scheduleWithFixedDelay(new Runnable() {@Overridepublic void run() {// 执行任务的业务代码System.out.println("执行任务" +" ,执行时间:" + LocalDateTime.now());}},2, // 初次执行间隔2, // 2s 执行一次TimeUnit.SECONDS);}
}

以上程序执行的结果为:

程序启动时间:2020-04-12T21:28:10.416

执行任务 ,执行时间:2020-04-12T21:28:12.421

执行任务 ,执行时间:2020-04-12T21:28:14.422

......

可以看出使用 ScheduledExecutorService#scheduleWithFixedDelay(...) 方法之后,会以某个频率一直循环执行延迟任务。

② DelayQueue 实现延迟任务

DelayQueue 是一个支持延时获取元素的无界阻塞队列,队列中的元素必须实现 Delayed 接口,并重写 getDelay(TimeUnit) 和 compareTo(Delayed) 方法,DelayQueue 实现延迟队列的完整代码如下:

public class DelayTest {public static void main(String[] args) throws InterruptedException {DelayQueue delayQueue = new DelayQueue();// 添加延迟任务delayQueue.put(new DelayElement(1000));delayQueue.put(new DelayElement(3000));delayQueue.put(new DelayElement(5000));System.out.println("开始时间:" +  DateFormat.getDateTimeInstance().format(new Date()));while (!delayQueue.isEmpty()){// 执行延迟任务System.out.println(delayQueue.take());}System.out.println("结束时间:" +  DateFormat.getDateTimeInstance().format(new Date()));}static class DelayElement implements Delayed {// 延迟截止时间(单面:毫秒)long delayTime = System.currentTimeMillis();public DelayElement(long delayTime) {this.delayTime = (this.delayTime + delayTime);}@Override// 获取剩余时间public long getDelay(TimeUnit unit) {return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Override// 队列里元素的排序依据public int compareTo(Delayed o) {if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {return 1;} else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {return -1;} else {return 0;}}@Overridepublic String toString() {return DateFormat.getDateTimeInstance().format(new Date(delayTime));}}
}

以上程序执行的结果为:

开始时间:2020-4-12 20:40:38

2020-4-12 20:40:39 

2020-4-12 20:40:41 

2020-4-12 20:40:43 

结束时间:2020-4-12 20:40:43

3.Redis 实现延迟任务

使用 Redis 实现延迟任务的方法大体可分为两类:通过 zset 数据判断的方式,和通过键空间通知的方式

① 通过数据判断的方式

我们借助 zset 数据类型,把延迟任务存储在此数据集合中,然后在开启一个无线循环查询当前时间的所有任务进行消费,实现代码如下(需要借助 Jedis 框架):

import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;public class DelayQueueExample {// zset keyprivate static final String _KEY = "myDelayQueue";public static void main(String[] args) throws InterruptedException {Jedis jedis = JedisUtils.getJedis();// 延迟 30s 执行(30s 后的时间)long delayTime = Instant.now().plusSeconds(30).getEpochSecond();jedis.zadd(_KEY, delayTime, "order_1");// 继续添加测试数据jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");// 开启延迟队列doDelayQueue(jedis);}/*** 延迟队列消费* @param jedis Redis 客户端*/public static void doDelayQueue(Jedis jedis) throws InterruptedException {while (true) {// 当前时间Instant nowInstant = Instant.now();long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒时间long nowSecond = nowInstant.getEpochSecond();// 查询当前时间的所有任务Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);for (String item : data) {// 消费任务System.out.println("消费:" + item);}// 删除已经执行的任务jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);Thread.sleep(1000); // 每秒轮询一次}}
}

② 通过键空间通知

默认情况下 Redis 服务器端是不开启键空间通知的,需要我们通过 config set notify-keyspace-events Ex 的命令手动开启,开启键空间通知后,我们就可以拿到每个键值过期的事件,我们利用这个机制实现了给每个人开启一个定时任务的功能,实现代码如下:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;public class TaskExample {public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称public static void main(String[] args) {Jedis jedis = JedisUtils.getJedis();// 执行定时任务doTask(jedis);}/*** 订阅过期消息,执行定时任务* @param jedis Redis 客户端*/public static void doTask(Jedis jedis) {// 订阅过期消息jedis.psubscribe(new JedisPubSub() {@Overridepublic void onPMessage(String pattern, String channel, String message) {// 接收到消息,执行定时任务System.out.println("收到消息:" + message);}}, _TOPIC);}
}

4.Netty 实现延迟任务

Netty 是由 JBOSS 提供的一个 Java 开源框架,它是一个基于 NIO 的客户、服务器端的编程框架,使用 Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty 相当于简化和流线化了网络应用的编程开发过程,例如:基于 TCP 和 UDP 的 socket 服务开发。

可以使用 Netty 提供的工具类 HashedWheelTimer 来实现延迟任务,实现代码如下。

首先在项目中添加 Netty 引用,配置如下:

<!-- https://mvnrepository.com/artifact/io.netty/netty-common -->
<dependency><groupId>io.netty</groupId><artifactId>netty-common</artifactId><version>4.1.48.Final</version>
</dependency>

Netty 实现的完整代码如下:

public class DelayTaskExample {public static void main(String[] args) {System.out.println("程序启动时间:" + LocalDateTime.now());NettyTask();}/*** 基于 Netty 的延迟任务*/private static void NettyTask() {// 创建延迟任务实例HashedWheelTimer timer = new HashedWheelTimer(3, // 时间间隔TimeUnit.SECONDS,100); // 时间轮中的槽数// 创建一个任务TimerTask task = new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {System.out.println("执行任务" +" ,执行时间:" + LocalDateTime.now());}};// 将任务添加到延迟队列中timer.newTimeout(task, 0, TimeUnit.SECONDS);}
}

以上程序执行的结果为:

程序启动时间:2020-04-13T10:16:23.033

执行任务 ,执行时间:2020-04-13T10:16:26.118

HashedWheelTimer 是使用定时轮实现的,定时轮其实就是一种环型的数据结构,可以把它想象成一个时钟,分成了许多格子,每个格子代表一定的时间,在这个格子上用一个链表来保存要执行的超时任务,同时有一个指针一格一格的走,走到那个格子时就执行格子对应的延迟任务,如下图所示:4d3472e5196011a5efd17d24ea9fbbe8.png(图片来源于网络)

以上的图片可以理解为,时间轮大小为 8,某个时间转一格(例如 1s),每格指向一个链表,保存着待执行的任务。

5.MQ 实现延迟任务

如果专门开启一个 MQ 中间件来执行延迟任务,就有点杀鸡用宰牛刀般的奢侈了,不过已经有了 MQ 环境的话,用它来实现延迟任务的话,还是可取的。

几乎所有的 MQ 中间件都可以实现延迟任务,在这里更准确的叫法应该叫延队列。本文就使用 RabbitMQ 为例,来看它是如何实现延迟任务的。

RabbitMQ 实现延迟队列的方式有两种:

  • 通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能;

  • 使用 rabbitmq-delayed-message-exchange 插件实现延迟功能。

注意:延迟插件 rabbitmq-delayed-message-exchange 是在 RabbitMQ 3.5.7 及以上的版本才支持的,依赖 Erlang/OPT 18.0 及以上运行环境。

由于使用死信交换器比较麻烦,所以推荐使用第二种实现方式 rabbitmq-delayed-message-exchange 插件的方式实现延迟队列的功能。

首先,我们需要下载并安装 rabbitmq-delayed-message-exchange 插件,下载地址:http://www.rabbitmq.com/community-plugins.html

选择相应的对应的版本进行下载,然后拷贝到 RabbitMQ 服务器目录,使用命令 rabbitmq-plugins enable rabbitmq_delayed_message_exchange 开启插件,在使用命令 rabbitmq-plugins list 查询安装的所有插件,安装成功如下图所示:

a4fdaf2cccfa07f9dfbb55bd9b090487.png

最后重启 RabbitMQ 服务,使插件生效。

首先,我们先要配置消息队列,实现代码如下:

import com.example.rabbitmq.mq.DirectConfig;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;@Configuration
public class DelayedConfig {final static String QUEUE_NAME = "delayed.goods.order";final static String EXCHANGE_NAME = "delayedec";@Beanpublic Queue queue() {return new Queue(DelayedConfig.QUEUE_NAME);}// 配置默认的交换机@BeanCustomExchange customExchange() {Map<String, Object> args = new HashMap<>();args.put("x-delayed-type", "direct");//参数二为类型:必须是x-delayed-messagereturn new CustomExchange(DelayedConfig.EXCHANGE_NAME, "x-delayed-message", true, false, args);}// 绑定队列到交换器@BeanBinding binding(Queue queue, CustomExchange exchange) {return BindingBuilder.bind(queue).to(exchange).with(DelayedConfig.QUEUE_NAME).noargs();}
}

然后添加增加消息的代码,具体实现如下:

import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;@Component
public class DelayedSender {@Autowiredprivate AmqpTemplate rabbitTemplate;public void send(String msg) {SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println("发送时间:" + sf.format(new Date()));rabbitTemplate.convertAndSend(DelayedConfig.EXCHANGE_NAME, DelayedConfig.QUEUE_NAME, msg, new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {message.getMessageProperties().setHeader("x-delay", 3000);return message;}});}
}

再添加消费消息的代码:

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;@Component
@RabbitListener(queues = "delayed.goods.order")
public class DelayedReceiver {@RabbitHandlerpublic void process(String msg) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println("接收时间:" + sdf.format(new Date()));System.out.println("消息内容:" + msg);}
}

最后,我们使用代码测试一下:

import com.example.rabbitmq.RabbitmqApplication;
import com.example.rabbitmq.mq.delayed.DelayedSender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.text.SimpleDateFormat;
import java.util.Date;@RunWith(SpringRunner.class)
@SpringBootTest
public class DelayedTest {@Autowiredprivate DelayedSender sender;@Testpublic void Test() throws InterruptedException {SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");sender.send("Hi Admin.");Thread.sleep(5 * 1000); //等待接收程序执行之后,再退出测试}
}

以上程序的执行结果如下:

发送时间:2020-04-13 20:47:51

接收时间:2020-04-13 20:47:54 

消息内容:Hi Admin.

从结果可以看出,以上程序执行符合延迟任务的实现预期。

6.使用 Spring 定时任务

如果你使用的是 Spring 或 SpringBoot 的项目的话,可以使用借助 Scheduled 来实现,本文将使用 SpringBoot 项目来演示 Scheduled 的实现,实现我们需要声明开启 Scheduled,实现代码如下:

@SpringBootApplication
@EnableScheduling
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

然后添加延迟任务,实现代码如下:

@Component
public class ScheduleJobs {@Scheduled(fixedDelay = 2 * 1000)public void fixedDelayJob() throws InterruptedException {System.out.println("任务执行,时间:" + LocalDateTime.now());}
}

此时当我们启动项目之后就可以看到任务以延迟了 2s 的形式一直循环执行,结果如下:

任务执行,时间:2020-04-13T14:07:53.349

任务执行,时间:2020-04-13T14:07:55.350 

任务执行,时间:2020-04-13T14:07:57.351

...

我们也可以使用 Corn 表达式来定义任务执行的频率,例如使用 @Scheduled(cron = "0/4 * * * * ?") 。

7.Quartz 实现延迟任务

Quartz 是一款功能强大的任务调度器,可以实现较为复杂的调度功能,它还支持分布式的任务调度。

我们使用 Quartz 来实现一个延迟任务,首先定义一个执行任务代码如下:

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;import java.time.LocalDateTime;public class SampleJob extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext jobExecutionContext)throws JobExecutionException {System.out.println("任务执行,时间:" + LocalDateTime.now());}
}

在定义一个 JobDetail 和 Trigger 实现代码如下:

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class SampleScheduler {@Beanpublic JobDetail sampleJobDetail() {return JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob").storeDurably().build();}@Beanpublic Trigger sampleJobTrigger() {// 3s 后执行SimpleScheduleBuilder scheduleBuilder =SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(1);return TriggerBuilder.newTrigger().forJob(sampleJobDetail()).withIdentity("sampleTrigger").withSchedule(scheduleBuilder).build();}
}

最后在 SpringBoot 项目启动之后开启延迟任务,实现代码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;/*** SpringBoot 项目启动后执行*/
public class MyStartupRunner implements CommandLineRunner {@Autowiredprivate SchedulerFactoryBean schedulerFactoryBean;@Autowiredprivate SampleScheduler sampleScheduler;@Overridepublic void run(String... args) throws Exception {// 启动定时任务schedulerFactoryBean.getScheduler().scheduleJob(sampleScheduler.sampleJobTrigger());}
}

以上程序的执行结果如下:

2020-04-13 19:02:12.331  INFO 17768 --- [  restartedMain] com.example.demo.DemoApplication         : Started DemoApplication in 1.815 seconds (JVM running for 3.088) 

任务执行,时间:2020-04-13T19:02:15.019

从结果可以看出在项目启动 3s 之后执行了延迟任务。

总结

本文讲了延迟任务的使用场景,以及延迟任务的 10 种实现方式:

  1. 手动无线循环;

  2. ScheduledExecutorService;

  3. DelayQueue;

  4. Redis zset 数据判断的方式;

  5. Redis 键空间通知的方式;

  6. Netty 提供的 HashedWheelTimer 工具类;

  7. RabbitMQ 死信队列;

  8. RabbitMQ 延迟消息插件 rabbitmq-delayed-message-exchange;

  9. Spring Scheduled;

  10. Quartz。

最后的话

俗话说:台上一分钟,台下十年功。本文内容皆为作者多年工作积累的结晶,以及爆肝呕心沥血的整理,如果觉得本文有帮助到你,请帮我分享出去,让更多的人看到,谢谢你。

15bc8f42646c0477e98f2d8eddb2e1b9.gif

往期推荐

e0f7b63e556e2d6faf2b593a2a2e7b14.png

最简单的6种防止数据重复提交的方法!(干货)


d0d50612b601e663765b2dea38557a71.png

下个十年高性能 JSON 库来了:fastjson2!


f911e0ccd4b79252456bb5a1c2089591.png

Spring Cloud OpenFeign 的 5 个优化小技巧!


66b4a6172462b6d925bbd071871aca62.gif

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

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

相关文章

面渣逆袭:Redis连环五十二问!三万字+八十图详解!

基础1.说说什么是Redis?Redis图标Redis是一种基于键值对&#xff08;key-value&#xff09;的NoSQL数据库。比一般键值对数据库强大的地方&#xff0c;Redis中的value支持string&#xff08;字符串&#xff09;、hash&#xff08;哈希&#xff09;、 list&#xff08;列表&…

EasyExcel太方便易用了,强烈推荐!

背景 系统中经常要导出大量的数据&#xff0c;格式基本上都是Excel&#xff0c;然而每次导表都是对系统内存的一次挑战。在Java领域&#xff0c;生成或解析Excel的框架比较有名的当属Apache的poi和jxl了。但使用它们&#xff0c;会面临着严重的内存损耗问题。如果系统的并发量还…

【端午】送3本书!

白天在公司搬砖&#xff0c;晚上到家赶紧给小伙伴们安排一波福利&#xff0c;这次送的书是 H 大新出的《深入理解Java核心技术&#xff1a;写给Java工程师的干货笔记&#xff08;基础篇&#xff09;》。书中介绍了普通Java工程师必须要学习的相关知识点&#xff0c;包括面向对象…

面试突击51:为什么单例一定要加 volatile?

.作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;单例模式的实现方法有很多种&#xff0c;如饿汉模式、懒汉模式、静态内部类和枚举等&#xff0c;当面试官问到“为什…

聊聊保证线程安全的10个小技巧

前言对于从事后端开发的同学来说&#xff0c;线程安全问题是我们每天都需要考虑的问题。线程安全问题通俗的讲&#xff1a;主要是在多线程的环境下&#xff0c;不同线程同时读和写公共资源&#xff08;临界资源&#xff09;&#xff0c;导致的数据异常问题。比如&#xff1a;变…

Raid控制器

转载于:https://blog.51cto.com/xuepengdou/1699799

并行计算机架构_计算机科学组织| 并行处理

并行计算机架构并行处理 (Parallel Processing) Parallel processing is processing of the data concurrently. We process the data concurrently to fulfill the demands of the increasingly high performance so that to achieve better throughput instead of processing…

15个必知的Mysql索引失效场景,别再踩坑了!

背景 无论你是技术大佬&#xff0c;还是刚入行的小白&#xff0c;时不时都会踩到Mysql数据库不走索引的坑。常见的现象就是&#xff1a;明明在字段上添加了索引&#xff0c;但却并未生效。前些天就遇到一个稍微特殊的场景&#xff0c;同一条SQL语句&#xff0c;在某些参数下生效…

干掉 Swagger UI,这款神器更好用、更高效!

事情是这样的&#xff1a;今天我们公司的后端说他接口写完了&#xff0c;并分享了一个接口文档给我。用的就是 Swagger UI 自动生成的那种接口文档&#xff0c;就像这种&#xff1a;这种 Swagger UI文档我每次看着就头大&#xff0c;毛病多多查看多级模型时要一级级点开在接口数…

Android UI ActionBar功能-ActionBarProvider的使用

分享功能是很多App都有一个功能&#xff0c;ActionBarProvider可以实现分享功能&#xff1a; 3.0以前的版 本和3.0以后的版 本的区别&#xff1a; public class MainActivity extends Activity {private ShareActionProvider provider;Overrideprotected void onCreate(Bundle …

面渣逆袭:MyBatis连环20问,这谁顶得住?

大家好&#xff0c;今天我们的主角是MyBatis&#xff0c;作为当前国内最流行的ORM框架&#xff0c;是我们这些crud选手最趁手的工具&#xff0c;赶紧来看看面试都会问哪些问题吧。基础1.说说什么是MyBatis?MyBatis logo先吹一下&#xff1a;Mybatis 是一个半 ORM&#xff08;对…

高并发下如何防重?

前言最近测试给我提了一个bug&#xff0c;说我之前提供的一个批量复制商品的接口&#xff0c;产生了重复的商品数据。追查原因之后发现&#xff0c;这个事情没想象中简单&#xff0c;可以说一波多折。1. 需求产品有个需求&#xff1a;用户选择一些品牌&#xff0c;点击确定按钮…

面试突击55:delete、drop、truncate有什么区别?

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在 MySQL 中&#xff0c;删除的方法总共有 3 种&#xff1a;delete、truncate、drop&#xff0c;而三者的用法和使用…

大厂也在用的 6种 数据脱敏方案,别做泄密内鬼

最近连着几天晚上在家总是接到一些奇奇怪怪的电话&#xff0c;“哥&#xff0c;你是 xxx 吧&#xff0c;我们这里是 xxx 高端男士私人会所...”&#xff0c;握草&#xff0c;我先是一愣&#xff0c;然后狠狠的骂了回去。一脸傲娇的转过头&#xff0c;面带微笑稍显谄媚&#xff…

在Python中使用OpenCV裁剪图像

What is Cropping? 什么是播种&#xff1f; Cropping is the removal of unwanted outer areas from a photographic or illustrated image. The process usually consists of the removal of some of the peripheral areas of an image to remove extraneous trash from the…

面渣逆袭:RocketMQ二十三问

1.为什么要使用消息队列呢&#xff1f;消息队列主要有三大用途&#xff0c;我们拿一个电商系统的下单举例&#xff1a;解耦&#xff1a;引入消息队列之前&#xff0c;下单完成之后&#xff0c;需要订单服务去调用库存服务减库存&#xff0c;调用营销服务加营销数据……引入消息…

Java日志性能那些事(转)

在任何系统中&#xff0c;日志都是非常重要的组成部分&#xff0c;它是反映系统运行情况的重要依据&#xff0c;也是排查问题时的必要线索。绝大多数人都认可日志的重要性&#xff0c;但是又有多少人仔细想过该怎么打日志&#xff0c;日志对性能的影响究竟有多大呢&#xff1f;…

33岁程序员的年中总结

作者 | 磊哥来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;人生在不同的阶段会有不同的生活方式和思考问题的角度&#xff0c;这是一件非常有趣的事~ 比如&#xff0c;我在 22 岁会想&…

数据科学中的简单线性回归

简单线性回归 (Simple Linear Regression) A simple regression model could be a linear approximation of a causative relationship between two or additional variables. Regressions models are extremely valuable, as theyre one in every of the foremost common ways…

鹅厂一面,有关 ThreadLocal 的一切

1. 底层结构ThreadLocal 底层有一个默认容量为 16 的数组组成&#xff0c;k 是 ThreadLocal 对象的引用&#xff0c;v 是要放到 TheadLocal 的值public void set(T value) {Thread t Thread.currentThread();ThreadLocalMap map getMap(t);if (map ! null)map.set(this, valu…