盘点Java中延时任务的多种实现方式

目录
  • 场景描述
  • 实现方式
    • 一、挂起线程
    • 二、ScheduledExecutorService 延迟任务线程池
    • 三、DelayQueue(延时队列)
    • 四、Redis-为key指定超时时长,并监听失效key
    • 五、时间轮
    • 六、消息队列-延迟队列

场景描述

①需要实现一个定时发布系统通告的功能,如何实现? ②支付超时,订单自动取消,如何实现?

实现方式

一、挂起线程

推荐指数:★★☆ 优点: JDK原生(JUC包下)支持,无需引入新的依赖; 缺点: (1)基于内存,应用重启(或宕机)会导致任务丢失 (2)基于内存挂起线程实现延时,不支持集群 (3)代码耦合性大,不易维护 (4)一个任务就要新建一个线程绑定任务的执行,容易造成资源浪费

①配置延迟任务专用线程池

/**
 * 线程池配置
 */
@Configuration
@EnableAsync
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolConfig {

	//ThreadPoolProperties的配置依据需求和服务器配置自行配置
    @Resource
    private ThreadPoolProperties threadPoolProperties;
    //延迟任务队列容量
    private final static int DELAY_TASK_QUEUE_CAPACITY = 100;

    @Bean
    public ThreadPoolTaskExecutor delayTaskExecutor() {
        log.info("start delayTaskExecutor");
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        //配置核心线程数
        threadPool.setCorePoolSize(threadPoolProperties.getCorePoolSize());
        //配置最大线程数
        threadPool.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
        //配置队列大小
        threadPool.setQueueCapacity(DELAY_TASK_QUEUE_CAPACITY);
        //线程最大存活时间
        threadPool.setKeepAliveSeconds (threadPoolProperties.getKeepAliveSeconds());
        //配置线程池中的线程的名称前缀
        threadPool.setThreadNamePrefix(threadPoolProperties.getThreadNamePrefix());

        // rejection-policy:当pool已经达到max size的时候执行的策略
        threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        //执行初始化
        threadPool.initialize();
        return threadPool;
    }
}

②创建延时任务

在需要执行的代码块创建延时任务

delayTaskExecutor.execute(() -> {
    try {
        //线程挂起指定时间
        TimeUnit.MINUTES.sleep(time);
        //执行业务逻辑
        doSomething();
    } catch (InterruptedException e) {
        log.error("线程被打断,执行业务逻辑失败");
    }
});

二、ScheduledExecutorService 延迟任务线程池

推荐指数:★★★ 优点: 代码简洁,JDK原生支持 缺点: (1)基于内存,应用重启(或宕机)会导致任务丢失 (2)基于内存存放任务,不支持集群 (3)一个任务就要新建一个线程绑定任务的执行,容易造成资源浪费

class Task implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getId()+":"+Thread.currentThread().getName());
        System.out.println("scheduledExecutorService====>>>延时器");
    }
}
public class ScheduleServiceTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService=new ScheduledThreadPoolExecutor(10);
        scheduledExecutorService.schedule(new Task(),1, TimeUnit.SECONDS);
        scheduledExecutorService.schedule(new Task(),2, TimeUnit.SECONDS);
        scheduledExecutorService.schedule(new Task(),1, TimeUnit.SECONDS);
    }
}

三、DelayQueue(延时队列)

推荐指数:★★★☆ 优点: (1)JDK原生(JUC包下)支持,无需引入新的依赖; (2)可以用一个线程对整个延时队列按序执行; 缺点: (1)基于内存,应用重启(或宕机)会导致任务丢失 (2)基于内存存放队列,不支持集群 (3)依据compareTo方法排列队列,调用take阻塞式的取出第一个任务(不调用则不取出),比较不灵活,会影响时间的准确性

①新建一个延时任务

public class DelayTask implements Delayed {

    private Integer taskId;

    private long executeTime;

    DelayTask(Integer taskId, long executeTime) {
        this.taskId = taskId;
        this.executeTime = executeTime;
    }

    /**
     * 该任务的延时时长
     * @param unit
     * @return
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return executeTime - System.currentTimeMillis();
    }

    @Override
    public int compareTo(Delayed o) {
        DelayTask t = (DelayTask) o;
        if (this.executeTime - t.executeTime <= 0) {
            return -1;
        } else {
            return 1;
        }
    }

    @Override
    public String toString() {
        return "延时任务{" +
                "任务编号=" + taskId +
                ", 执行时间=" + new Date(executeTime) +
                '}';
    }

    /**
     * 执行具体业务代码
     */
    public void doTask(){
        System.out.println(this+":");
        System.out.println("线程ID-"+Thread.currentThread().getId()+":线程名称-"+Thread.currentThread().getName()+":do something!");
    }
}

②执行延时任务

public class TestDelay {
    public static void main(String[] args) throws InterruptedException {
        // 新建3个任务,并依次设置超时时间为 30s 10s 60s
        DelayTask d1 = new DelayTask(1, System.currentTimeMillis() + 3000L);
        DelayTask d2 = new DelayTask(2, System.currentTimeMillis() + 1000L);
        DelayTask d3 = new DelayTask(3, System.currentTimeMillis() + 6000L);

        DelayQueue<DelayTask> queue = new DelayQueue<>();
        queue.add(d1);
        queue.add(d2);
        queue.add(d3);

        System.out.println("开启延时队列时间:" + new Date()+"\n");

        // 从延时队列中获取元素
        while (!queue.isEmpty()) {
            queue.take().doTask();
        }
        System.out.println("\n任务结束");
    }
}

执行结果:

四、Redis-为key指定超时时长,并监听失效key

推荐指数:★★★☆ 优点: 对于有依赖redis的业务且有延时任务的需求,能够快速对接 缺点: (1)客户端断开后重连会导致所有事件丢失 (2)高并发场景下,存在大量的失效key场景会导出失效时间存在延迟 (3)若有多个监听器监听该key,是会重复消费这个过期事件的,需要特定逻辑判断

① 修改Redis配置文件并重启Redis

notify-keyspace-events Ex

注意: redis配置文件不能有空格,否则会启动报错

②Java中关于Redis的配置类

redisTemplate实例bean需要自定义生成; RedisMessageListenerContainer 是redis-key过期监听需要的监听器容器;

@Configuration
@Slf4j
public class RedisConfiguration {
    /**
     * Redis配置
     * @param factory
     * @return
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();

        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(redisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(redisSerializer);
        //key hashmap序列化
        template.setHashKeySerializer(redisSerializer);

        return template;
    }

    /**
     * 消息监听器容器bean
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisMessageListenerContainer container(LettuceConnectionFactory connectionFactory) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}

③监听器代码

@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    private static final String TEST_REDIS_KEY = "testExpired";
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer,
                                      RedisTemplate redisTemplate) {
        super(listenerContainer);
        /**
         * 设置一个Redis延迟过期key(key名:testExpired,过期时间:30秒)
         */
        redisTemplate.opsForValue().set(TEST_REDIS_KEY, "1", 20, TimeUnit.SECONDS);
        log.info("设置redis-key");
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        try {
            String expiredKey = message.toString();
            if (TEST_REDIS_KEY.equals(expiredKey)) {
                //业务处理
                log.info(expiredKey + "过期,触发回调");
            }
        } catch (Exception e) {
            log.error("key 过期通知处理异常,{}", e);
        }

    }
}

测试结果:

五、时间轮

推荐指数:★★★★ 优点: (1)对于大量定时任务,时间轮可以仅用一个工作线程对编排的任务进行顺序运行; (2)自动运行,可以自定义时间轮每轮的tick数,tick间隔,灵活且时间精度可控 缺点: (1)基于内存,应用重启(或宕机)会导致任务丢失 (2)基于内存存放任务,不支持集群

public class WheelTimerTest {

    public static void main(String[] args) {

        //设置每个格子是 100ms, 总共 256 个格子
        HashedWheelTimer hashedWheelTimer = new HashedWheelTimer(100, TimeUnit.MILLISECONDS, 256);

        //加入三个任务,依次设置超时时间是 10s 5s 20s

        System.out.println("加入一个任务,ID = 1, time= " + LocalDateTime.now());
        hashedWheelTimer.newTimeout(timeout -> {
            System.out.println(Thread.currentThread().getName());
            System.out.println("执行一个任务,ID = 1, time= " + LocalDateTime.now());
        }, 10, TimeUnit.SECONDS);

        System.out.println("加入一个任务,ID = 2, time= " + LocalDateTime.now());
        hashedWheelTimer.newTimeout(timeout -> {
            System.out.println(Thread.currentThread().getName());
            System.out.println("执行一个任务,ID = 2, time= " + LocalDateTime.now());
        }, 5, TimeUnit.SECONDS);

        System.out.println("加入一个任务,ID = 3, time= " + LocalDateTime.now());
        hashedWheelTimer.newTimeout(timeout -> {
            System.out.println(Thread.currentThread().getName());
            System.out.println("执行一个任务,ID = 3, time= " + LocalDateTime.now());
        }, 20, TimeUnit.SECONDS);
        System.out.println("加入一个任务,ID = 4, time= " + LocalDateTime.now());
        hashedWheelTimer.newTimeout(timeout -> {
            System.out.println(Thread.currentThread().getName());
            System.out.println("执行一个任务,ID = 4, time= " + LocalDateTime.now());
        }, 20, TimeUnit.SECONDS);

        System.out.println("等待任务执行===========");
    }
}

六、消息队列-延迟队列

针对任务丢失的代价过大,高并发的场景 推荐指数:★★★★ 优点: 支持集群,分布式,高并发场景; 缺点: 引入额外的消息队列,增加项目的部署和维护的复杂度。

场景:为一个委托指定期限,委托到期后,委托关系终止,相关业务权限移交回原拥有者 这里采用的是RabbitMq的死信队列加TTL消息转化为延迟队列的方式(RabbitMq没有延时队列)

①声明一个队列设定其的死信队列

@Configuration
public class MqConfig {
    public static final String GLOBAL_RABBIT_TEMPLATE = "rabbitTemplateGlobal";

    public static final String DLX_EXCHANGE_NAME = "dlxExchange";
    public static final String AUTH_EXCHANGE_NAME = "authExchange";

    public static final String DLX_QUEUE_NAME = "dlxQueue";
    public static final String AUTH_QUEUE_NAME = "authQueue";
    public static final String DLX_AUTH_QUEUE_NAME = "dlxAuthQueue";

    @Bean
    @Qualifier(GLOBAL_RABBIT_TEMPLATE)
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    @Qualifier(AUTH_EXCHANGE_NAME)
    public Exchange authExchange() {
        return ExchangeBuilder.directExchange (AUTH_EXCHANGE_NAME).durable (true).build ();
    }

    /**
     * 死信交换机
     * @return
     */
    @Bean
    @Qualifier(DLX_EXCHANGE_NAME)
    public Exchange dlxExchange() {
        return ExchangeBuilder.directExchange (DLX_EXCHANGE_NAME).durable (true).build ();
    }

    /**
     * 记录日志的死信队列
     * @return
     */
    @Bean
    @Qualifier(DLX_QUEUE_NAME)
    public Queue dlxQueue() {
        // Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
        return QueueBuilder.durable (DLX_QUEUE_NAME).build ();
    }

    /**
     * 委托授权专用队列
     * @return
     */
    @Bean
    @Qualifier(AUTH_QUEUE_NAME)
    public Queue authQueue() {
        return QueueBuilder
                .durable (AUTH_QUEUE_NAME)
                .withArgument("x-dead-letter-exchange", DLX_EXCHANGE_NAME)
                .withArgument("x-dead-letter-routing-key", "dlx_auth")
                .build ();
    }

    /**
     * 委托授权专用死信队列
     * @return
     */
    @Bean
    @Qualifier(DLX_AUTH_QUEUE_NAME)
    public Queue dlxAuthQueue() {
        // Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
        return QueueBuilder
                .durable (DLX_AUTH_QUEUE_NAME)
                .withArgument("x-dead-letter-exchange", DLX_EXCHANGE_NAME)
                .withArgument("x-dead-letter-routing-key", "dlx_key")
                .build ();
    }

    @Bean
    public Binding bindDlxQueueExchange(@Qualifier(DLX_QUEUE_NAME) Queue dlxQueue, @Qualifier(DLX_EXCHANGE_NAME) Exchange dlxExchange){
        return BindingBuilder.bind (dlxQueue).to (dlxExchange).with ("dlx_key").noargs ();
    }

    /**
     * 委托授权专用死信队列绑定关系
     * @param dlxAuthQueue
     * @param dlxExchange
     * @return
     */
    @Bean
    public Binding bindDlxAuthQueueExchange(@Qualifier(DLX_AUTH_QUEUE_NAME) Queue dlxAuthQueue, @Qualifier(DLX_EXCHANGE_NAME) Exchange dlxExchange){
        return BindingBuilder.bind (dlxAuthQueue).to (dlxExchange).with ("dlx_auth").noargs ();
    }

    /**
     * 委托授权专用队列绑定关系
     * @param authQueue
     * @param authExchange
     * @return
     */
    @Bean
    public Binding bindAuthQueueExchange(@Qualifier(AUTH_QUEUE_NAME) Queue authQueue, @Qualifier(AUTH_EXCHANGE_NAME) Exchange authExchange){
        return BindingBuilder.bind (authQueue).to (authExchange).with ("auth").noargs ();
    }

}

②发送含过期时间的消息

向授权交换机,发送路由为"auth"的消息(指定了业务所需的超时时间) =》发向MqConfig.AUTH_QUEUE_NAME 队列

rabbitTemplate.convertAndSend(MqConfig.AUTH_EXCHANGE_NAME, "auth", "类型:END,信息:{id:1,fromUserId:111,toUserId:222,beginData:20201204,endData:20211104}", message -> {
            /**
             * MessagePostProcessor:消息后置处理
             * 为消息设置属性,然后返回消息,相当于包装消息的类
             */

            //业务逻辑:过期时间=xxxx
            String ttl = "5000";
            //设置消息的过期时间
            message.getMessageProperties ().setExpiration (ttl);
            return message;
        });

③超时后队列MqConfig.AUTH_QUEUE_NAME会将消息转发至其配置的死信路由"dlx_auth",监听该死信队列即可消费定时的消息

 	/**
     * 授权定时处理
     * @param channel
     * @param message
     */
    @RabbitListener(queues = MqConfig.DLX_AUTH_QUEUE_NAME)
    public void dlxAuthQ(Channel channel, Message message) throws IOException {
        System.out.println ("\n死信原因:" + message.getMessageProperties ().getHeaders ().get ("x-first-death-reason"));
        //1.判断消息类型:1.BEGIN 2.END
        try {
            //2.1 类型为授权到期(END)
            //2.1.1 修改报件办理人
            //2.1.2 修改授权状态为0(失效)

            //2.2 类型为授权开启(BEGIN)
            //2.2.1 修改授权状态为1(开启)
            System.out.println (new String(message.getBody (), Charset.forName ("utf8")));
            channel.basicAck (message.getMessageProperties ().getDeliveryTag (),  false);
            System.out.println ("已处理,授权相关信息修改成功");
        } catch (Exception e) {
            //拒签消息
            channel.basicNack (message.getMessageProperties ().getDeliveryTag (), false, false);
            System.out.println ("授权相关信息处理失败, 进入死信队列记录日志");
        }
    }

以上就是盘点Java中延时任务的多种实现方式的详细内容,更多关于Java延时任务的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java利用Netty时间轮实现延时任务

    目录 一.时间轮算法简介 二.时间轮hello-world 三.异步任务线程池 四.时间轮优缺点 知识点补充 一.时间轮算法简介 为了大家能够理解下文中的代码,我们先来简单了解一下netty时间轮算法的核心原理 时间轮算法名副其实,时间轮就是一个环形的数据结构,类似于表盘,将时间轮分成多个bucket(比如:0-8).假设每个时间轮轮片的分隔时间段tickDuration=1s(即:指针经过每个格子花费时间是 1 s),当前的时间bucket=3,那么在18秒后需要被执行的任务需要落到((3+1

  • Java DelayQueue实现延时任务的示例详解

    目录 一.DelayQueue的应用原理 二.订单延时任务的实现 三.订单处理 四.优缺点 一.DelayQueue的应用原理 DelayQueue是一个无界的BlockingQueue的实现类,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走. BlockingQueue即阻塞队列,java提供的面向多线程安全的队列数据结构,当队列内元素数量为0的时候,试图从队列内获取元素的线程将被阻塞或者抛出异常. 这里的“无界”队列,是指队列的元素数量不存在上限,队列的容量

  • Java利用redis zset实现延时任务详解

    目录 一.实现原理 二.准备工作 三.代码实现 四.优缺点 所谓的延时任务给大家举个例子:你买了一张火车票,必须在30分钟之内付款,否则该订单被自动取消.「订单30分钟不付款自动取消,这个任务就是一个延时任务.」   我之前已经写过2篇关于延时任务的文章: <通过DelayQueue实现延时任务> <基于netty时间轮算法实战> 这两种方法都有一个缺点:都是基于单体应用的内存的方式运行延时任务的,一旦出现单点故障,可能出现延时任务数据的丢失.所以此篇文章给大家介绍实现延时任务的第

  • Java处理延时任务的常用几种解决方案

    目录 前言 数据库轮询 原理 优缺点 Java延迟队列 Reids监听失效key 创建监听类,实现MessageListener接口 RocketMq延迟消息 总结 前言 项目中经常会遇到如下的需求: 创建订单30分钟未支付,订单自动取消. 订单支付成功后,1分钟后给用户发送短信,提醒用户评价. … 针对延时任务需求,我们可以采用如下的解决方案: 数据库轮询 原理 通过一个线程定时的扫描数据库当天创建的订单,根据订单的创建时间来判断订单是否超时,针对超时订单进行相关的更新操作.实现技术采用Spr

  • 一文带你深入了解Java中延时任务的实现

    目录 概述 JAVA DelayQueue DelayQueue的实现原理 DelayQueue实现延时队列的优缺点 时间轮算法 时间轮的具体实现 进阶优化版时间轮算法 时间轮算法的应用 小结 redis延时队列 mq延时队列 rocketmq延时消息 rocketmq的精准延时消息 总结 概述 延时任务相信大家都不陌生,在现实的业务中应用场景可以说是比比皆是.例如订单下单15分钟未支付直接取消,外卖超时自动赔付等等.这些情况下,我们该怎么设计我们的服务的实现呢? 笨一点的方法自然是定时任务去数

  • Java DelayQueue实现任务延时示例讲解

    在项目中有使用到延时队列的场景,做个简单的记录说明:首先DelayQueue实现了BlockingQueue,加入其中的元素必须实现Delayed接口: 当生产者元素调用put往其中加入元素时,出发Delayed接口的compareTo方法进行排序,这个排序是按照时间的,按照计划执行的时间排序,先执行的在前面,后执行的排后面:消费者获取元素时,调用getDelay方法返回的值大于0,则消费者线程wait返回的这个时间后,再从队列头部取出元素:下面是个简单的例子 import org.jetbra

  • 盘点Java中延时任务的多种实现方式

    目录 场景描述 实现方式 一.挂起线程 二.ScheduledExecutorService 延迟任务线程池 三.DelayQueue(延时队列) 四.Redis-为key指定超时时长,并监听失效key 五.时间轮 六.消息队列-延迟队列 场景描述 ①需要实现一个定时发布系统通告的功能,如何实现? ②支付超时,订单自动取消,如何实现? 实现方式 一.挂起线程 推荐指数:★★☆ 优点: JDK原生(JUC包下)支持,无需引入新的依赖: 缺点: (1)基于内存,应用重启(或宕机)会导致任务丢失 (2

  • 分析java中全面的单例模式多种实现方式

    一.单例模式的思想 想整理一些 java 并发相关的知识,不知道从哪开始,想起了单例模式中要考虑的线程安全,就从单例模式开始吧.以前写过单例模式,这里再重新汇总补充整理一下,单例模式的多种实现. 单例模式的主要思想是: 将构造方法私有化( 声明为 private ),这样外界不能随意 new 出新的实例对象: 声明一个私有的静态的实例对象,供外界使用: 提供一个公开的方法,让外界获得该类的实例对象 这种说法看上去没错,但也好像不太准确.其实,就算外界能随意 new 出新的实例对象,但只要我们保证

  • Java中生成随机数的4种方式与区别详解

    目录 在 Java 中,生成随机数的场景有很多,所以本文我们就来盘点一下 4 种生成随机数的方式,以及它们之间的区别和每种生成方式所对应的场景. 1.Random Random 类诞生于 JDK 1.0,它产生的随机数是伪随机数,也就是有规则的随机数.Random 使用的随机算法为 linear congruential pseudorandom number generator (LGC) 线性同余法伪随机数.在随机数生成时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的

  • Java中定时任务的6种实现方式

    目录 1.线程等待实现 2.JDK自带Timer实现 2.1 核心方法 2.2使用示例 2.2.1指定延迟执行一次 2.2.2固定间隔执行 2.2.3固定速率执行 2.3 schedule与scheduleAtFixedRate区别 2.3.1schedule侧重保持间隔时间的稳定 2.3.2scheduleAtFixedRate保持执行频率的稳定 2.4 Timer的缺陷 3.JDK自带ScheduledExecutorService 3.1 scheduleAtFixedRate方法 3.2

  • Java中实现线程的三种方式及对比_动力节点Java学院整理

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. (2)创建Thread子类的实例,即创建了线程对象. (3)调用线程对象的start()方法来启动该线程. package com.thread; public class FirstThreadTest extends Thread{ int i = 0; //重写run方法,run方法的方

  • 浅谈java中String的两种赋值方式的区别

    类似普通对象,通过new创建字符串对象.String str = new String("Hello"); 内存图如下图所示,系统会先创建一个匿名对象"Hello"存入堆内存(我们暂且叫它A),然后new关键字会在堆内存中又开辟一块新的空间,然后把"Hello"存进去,并且把地址返回给栈内存中的str, 此时A对象成为了一个垃圾对象,因为它没有被任何栈中的变量指向,会被GC自动回收. 直接赋值.如String str = "Hello&

  • 浅谈Java中实现深拷贝的两种方式—clone() & Serialized

    clone() 方法麻烦一些,需要将所有涉及到的类实现声明式接口 Cloneable,并覆盖Object类中的clone()方法,并设置作用域为public(这是为了其他类可以使用到该clone方法). 序列化的方法简单,需要将所有涉及到的类实现接口Serializable package b1ch06.clone; import java.io.Serializable; class Car implements Cloneable, Serializable { private String

  • Java中遍历ConcurrentHashMap的四种方式详解

    这篇文章主要介绍了Java中遍历ConcurrentHashMap的四种方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 方式一:在for-each循环中使用entries来遍历 System.out.println("方式一:在for-each循环中使用entries来遍历");for (Map.Entry<String, String> entry: map.entrySet()) { System.out.pr

  • Java中switch的三种用法方式

    Java中switch的三种用法详解: switch居然有三种方式 ? 作为一个接触java不久的人来说,这确实让我吃了一惊! 根据版本,在java14开始, switch语句有了一个很大的调整, 这就让swicth语句有了更多的操作和选择,在代码上,更加的简便灵活, 让我们试试这神奇的switch吧! 使用switch这个关键词, 我们可以很好的解决if-else 中多重选择的尴尬场面! Java中switch的三种用法详解: switch 标准方式 switch - > 用法: switch

随机推荐