• java 常用工具类


    Java常用类

    Optional

    在我们的开发中,NullPointerException可谓是随时随处可见,为了避免空指针异常,我们常常需要进行 一
    些防御式的检查,所以在代码中常常可见if(obj != null) 这样的判断。幸好在JDK1.8中,java为我们提供了
    一个Optional类,Optional类能让我们省掉繁琐的非空的判断。下面先说一下Optional中为我们提供的方法。

    image-20220831233206208

    反面示例:

    interface IMessage{
        void echo(String msg);
    }
    class Factory{
        public static IMessage getInstance1(){
            return (msg -> System.out.println("msg = " + msg)); // 正常
            
        }
    }
    public class Demo {
        public static void main(String[] args) {
            IMessage message = Factory.getInstance1();
            if(message!=null){ // 不为null才调用
                message.echo("你好,小弟弟");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20220901231635533

    正面示例:

    interface IMessage {
        void echo(String msg);
    }
    
    class Factory {
        public static Optional<IMessage> getInstance2() {
            return Optional.of((msg -> System.out.println(msg))); // 保存到 Optional中 如果保存的为null的话,还是会发生空指针异常
        }
    }
    
    public class Demo {
        public static void main(String[] args) {
            IMessage message = Factory.getInstance2().get();// 取出 Optional中的数据
            message.echo("你好,小弟弟");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这里想要表达的意思就是,保存到Optional中的数据为null,只会在赋值是出现空指针异常,而不会等到调用是才出现,增强了业务的健壮性。

    ThreadLocal

    ​ 从名字我们就可以看到ThreadLocal 叫做本地线程变量,意思是说,ThreadLocal 中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal 为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。

    ​ 从字面意思很容易理解,但是实际角度就没那么容易了,作为一个面试常问的点,使用场景也是很丰富。

    • 1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
    • 2、线程间数据隔离
    • 3、进行事务操作,用于存储线程事务信息。
    • 4、数据库连接,Session会话管理。

    1. 常用方法

    image-20220901124936453

    2. ThreadLocal怎么用?

    下面我先举一个反面例子,加深大家的理解。

    启动三个线程,遍历values数组,然后看他们的输出结果。

    @Data
    class Message {
        public String content;
    }
    class MessagePrint { // 输出结果
        public static void print() {
            System.out.println("【MessagePrint】" + Resource.message.content);
        }
    }
    
    /**
     * 中间类
     */
    class Resource {
        static Message message;
    }
    /**
     * 测试
     * @author jiejie
     * @date 2022/09/01
     */
    public class Demo1 {
        public static void main(String[] args) {
            String[] values = {"你好,弟弟", "你好,妹妹", "你好,姐姐"};
            for (String value : values) {
                new Thread(() -> {
                    Resource.message = new Message();
                    Resource.message.setContent(value);
                    MessagePrint.print();
                }).start();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    结果:

    image-20220901124138229

    可以看到,输出的结果怎么是一样的呢,我们不是遍历输出的吗。这就要谈到我们的线程安全的问题,简单来说,多个线程同时对某个对象进行赋值就会存在线程安全的问题,其实相对于这个问题,我们可以加锁来解决这个问题。今天我们使用另外一个方法,就是上面提到的ThreadLocal。请看:

    修改Resource:

    /**
     * 中间类
     */
    class Resource {
    	
        private static ThreadLocal<Message> threadLocal = new ThreadLocal<>();
    
        public static Message getMessage() {
            return threadLocal.get();
        }
    
        public static void setMessage(Message message) {
            threadLocal.set(message);
        }
    
        public static void removeMessage() {
            threadLocal.remove();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    class MessagePrint { // 输出
    
        public static void print() {
            System.out.println("【MessagePrint】" + Resource.getMessage().content);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class Demo1 {
        public static void main(String[] args) {
            String[] values = {"你好,弟弟", "你好,妹妹", "你好,姐姐"};
    
            for (String value : values) {
                new Thread(() -> {
                        Resource.setMessage(new Message());
                        Resource.getMessage().setContent(value);
                        MessagePrint.print();
                }).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    程序执行结果:

    image-20220901124719338

    可以看到,我们的目的实现了,有没有发现输出的顺序与values数组的顺序并不一致,这是由于我们线程启动的顺序决定的。

    我们这里就体现了线程间对于变量的隔离。

    定时任务

    在开发过程中,经常性需要一些定时或者周期性的操作。而在Java中则使用Timer对象完成定时计划任务功能。

    定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以Timer对象一般又和多线程技术结合紧密。

    由于Timer是Java提供的原生Scheduler(任务调度)工具类,不需要导入其他jar包,使用起来方便高效,非常快捷。

    image-20220902103135845

    参数说明:

    • task:所要执行的任务,需要extends TimeTask override run()
    • time/firstTime:首次执行任务的时间
    • period:周期性执行Task的时间间隔,单位是毫秒
    • delay:执行task任务前的延时时间,单位是毫秒 很显然,通过上述的描述,我们可以实现: 延迟多久后执行一次任务;指定时间执行一次任务;延迟一段时间,并周期性执行任务;指定时间,并周期性执行任务;

    1. timer

    1. 实现TimerTask**(需要执行什么任务**)的run方法, 明确要做什么
      可以继承实现, 也可用匿名内部类
    2. new 一个Timer
    3. 调用Timer实例的 schedule 或 scheduleAtFixedRate 方法
      将TimerTask放入Timer,并指定开始时间 和 间隔时间

    简单用法

    public class Demo {
        public static void main(String[] args) {
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("【定时任务】定时执行");
                }
            }, 1000, 2000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    执行结果:

    【定时任务】定时执行
    【定时任务】定时执行
    【定时任务】定时执行
    
    • 1
    • 2
    • 3
    1.2 schedule和scheduleAtFixedRate有什么区别?

    scheduleAtFixedRate:每次执行时间为上一次任务开始起向后推一个period间隔,也就是说下次执行时间相对于上一次任务开始的时间点,因此执行时间不会延后,但是存在任务并发执行的问题(简单来说,就是当任务阻塞,下次任务开始的时间不会受阻塞影响,而推迟下次任务执行时间。)。

    schedule:每次执行时间为上一次任务结束后推一个period间隔,也就是说下次执行时间相对于上一次任务结束的时间点,因此执行时间会不断延后(回受阻塞影响)。

    1.3:如果执行task发生异常,是否会影响其他task的定时调度?

    如果TimeTask抛出RuntimeException,那么Timer会停止所有任务的运行!

    1.4 Timer的一些缺陷?

    ​ 前面已经提及到Timer背后是一个单线程,因此Timer存在管理并发任务的缺陷:所有任务都是由同一个线程来调度,所有任务都是串行执行,意味着同一时间只能有一个任务得到执行,而前一个任务的延迟或者异常会影响到之后的任务。 其次,Timer的一些调度方式还算比较简单,无法适应实际项目中任务定时调度的复杂度。

    2 JDK对定时任务调度的线程池支持:ScheduledExecutorService

    ​ 由于Timer存在的问题,JDK5之后便提供了基于线程池的定时任务调度:ScheduledExecutorService。 设计理念:每一个被调度的任务都会被线程池中的一个线程去执行,因此任务可以并发执行,而且相互之间不受影响。

    我们直接看例子:

    image-20220902125525163

    执行结果:

    【定时任务】定时执行Fri Sep 02 12:54:02 CST 2022
    【定时任务】定时执行Fri Sep 02 12:54:04 CST 2022
    【定时任务】定时执行Fri Sep 02 12:54:06 CST 2022

    3 定时任务大哥:Quartz

    虽然ScheduledExecutorService对Timer进行了线程池的改进,但是依然无法满足复杂的定时任务调度场景。因此OpenSymphony提供了强大的开源任务调度框架:Quartz。Quartz是纯Java实现,而且作为Spring的默认调度框架,由于Quartz的强大的调度功能、灵活的使用方式、还具有分布式集群能力,可以说Quartz出马,可以搞定一切定时任务调度!

    3.1 核心:

    任务 Job

    我们想要调度的任务都必须实现 org.quartz.job 接口,然后实现接口中定义的 execute( ) 方法即可,类似于TimerTask

    触发器 Trigger

    Trigger 作为执行任务的调度器。我们如果想要凌晨1点执行备份数据的任务,那么 Trigger 就会设置凌晨1点执行该任务。其中 Trigger 又分为 SimpleTriggerCronTrigger 两种

    调度器 Scheduler

    Scheduler 为任务的调度器,它会将任务 Job 及触发器 Trigger 整合起来,负责基于 Trigger 设定的时间来执行 Job

    3.2 使用Quartz

    导入依赖:

    		<!--quartz-->
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz</artifactId>
                <version>2.3.2</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建任务类:

    public class TestJob implements Job{
    
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            System.out.println("【定时任务】定时执行"+new Date());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试

    class TestScheduler {
        public static void main(String[] args) throws SchedulerException {
            // 获取默认任务调度器
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            // 定义Job(任务)实例
            JobDetail testJob = JobBuilder.newJob(TestJob.class)
                    .withIdentity("测试任务").build();
            // 定义触发器
            Trigger simpleTrigger = TriggerBuilder.newTrigger()
                    .withIdentity("测试任务的触发器")
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1))
                    .build();
            // 使用触发器调度任务的执行
            scheduler.scheduleJob(testJob, simpleTrigger);
            scheduler.start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    image-20220902132836415

    经过上面的简单使用,我们再来了解下它的结构吧

    image-20220902153106561

    图中可知,还有一种触发器 CronTrigger,下面简单使用一下吧。

    Cron表达式用法

    测试类 任务类不变,修改测试类即可

    class TestScheduler {
        public static void main(String[] args) throws SchedulerException {
            // 获取默认任务调度器
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            // 定义Job(任务)实例
            JobDetail testJob = JobBuilder.newJob(TestJob.class)
                    .withIdentity("测试任务").build();
            // 定义触发器
            CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                    .withIdentity("name", "group")
                    .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * ? * *")).build();
            // 使用触发器调度任务的执行
            scheduler.scheduleJob(testJob, cronTrigger);
            scheduler.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    (附源码)php二手服装网站 毕业设计 201711
    什么时候使用继承,好莱坞原则(设计模式与开发实践 P11+)
    锐捷网络有限公司自工序完结项目圆满结束
    和数集团首款自研虚拟数字人上线,“始祖龙”带你跨山海,链未来
    【c/c++学习】与或非的奇技淫巧
    dnslog注入_dnslog盲注
    C++11的互斥量
    操作系统 - 进程
    KubeSphere 社区双周报 | 2022-10-28
    极智编程 | 谈谈 C++ 中容器 map 和 unordered_map 的区别
  • 原文地址:https://blog.csdn.net/qq_50975965/article/details/126665013