Java常用类库中(ThreadLocal、Comparable比较器、AutoCloseable、Optional空处理)附带相关面试题

简介: 1.ThreadLocal线程独立,2.Comparable比较器与Comparetor,3.AutoCloseable接口,4.Optional空处理



1.ThreadLocal线程独立

如果说每一个用户代表一个线程,那么当用户去访问自己的独有的比如id时候就应该正确返回自己的id。ThreadLocal就好比一个大储物柜里面有许多小的储物柜,每一个线程拥有自己的储物柜,方便自己拿取自己的东西,这样就能保证数据之间的独立与安全

ThreadLocal类常用的方法:

方法 描述
T get() 返回当前线程关联的变量副本的值。如果没有设置过,则返回null
void set(T value) 将当前线程关联的变量副本设置为指定的值。
void remove() 删除当前线程关联的变量副本。
protected T initialValue() 提供一个初始化值,用于首次访问时创建变量副本的情况。子类可以重写此方法来指定自定义的初始化值。
static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) 创建一个带有初始值提供者的ThreadLocal实例。每个线程在首次访问时,都会使用提供者生成一个初始值。
static ThreadLocalRandom current() 返回当前线程关联的ThreadLocalRandom实例

案例要求:

设置三个线程ABC每一个线程要有独立的资源。并且每个线程输出自己独立的资源

案例代码以及分析:

package Example1511;
class Message {
    private String inFo;
    public void setInFo(String inFo) {
        this.inFo = inFo;
    }
    public String getInFo() {
        return inFo;
    }
}
class Channel {
    private static final ThreadLocal<Message> ThreadLocal = new ThreadLocal<Message>();
    //    构造方法独立,防止外部访问
    private Channel() {
    }
    //    存放对象到储物盒子ThreadLocal里面
    public static void setMessage(Message msg) {
        ThreadLocal.set(msg);
    }
    //    获取自己盒子里面的对象的信息
    public static void send() {
        System.out.println(Thread.currentThread().getName() + "获取信息:" + ThreadLocal.get().getInFo());
    }
}
public class javaDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            Message msg = new Message();
            msg.setInFo("我是线程A特有的资源哦");
            Channel.setMessage(msg);
            Channel.send();
        }, "线程A").start();
        new Thread(() -> {
            Message msg = new Message();
            msg.setInFo("我是线程B特有的资源哦");
            Channel.setMessage(msg);
            Channel.send();
        }, "线程B").start();
        new Thread(() -> {
            Message msg = new Message();
            msg.setInFo("我是线程C特有的资源哦");
            Channel.setMessage(msg);
            Channel.send();
        }, "线程C").start();
    }
}

image.gif

image.gif编辑

为什么要用private static final ThreadLocal<Message> ThreadLocal = new ThreadLocal<Message>();static final修饰为了什么目的?

在这段代码中,使用private static final ThreadLocal<Message> ThreadLocal = new ThreadLocal<Message>();ThreadLocal声明为私有静态常量。

private修饰符表示ThreadLocal只能在Channel类内部访问,禁止外部直接访问,从而保证了对ThreadLocal的访问权限的封装性。

static修饰符表示ThreadLocal是一个类级别的变量,而不是实例级别的变量。这意味着所有的Channel实例共享同一个ThreadLocal对象,而不是每个实例都拥有自己的副本。这样做的目的是为了确保线程间的隔离效果。

final修饰符表示ThreadLocal引用的对象是不可变的,一旦引用被赋值,就无法再更改其指向的对象。这里将ThreadLocal声明为常量是为了防止意外修改ThreadLocal的引用,确保程序的稳定性和可靠性。

总的来说,将ThreadLocal声明为private static final的目的是为了实现线程间的资源隔离并确保其访问权限的封装、共享和稳定性。

面试题:ThreadLocal 是什么?有哪些使用场景?

    1. ThreadLocal 是一个本地线程副本变量工具类,在每个线程中都创建了一个 ThreadLocalMap 对象,简单说 ThreadLocal 就是一种以空间换时间的做法,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
    2. 原理:线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
    3. 经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。

    面试题:请谈谈 ThreadLocal 是怎么解决并发安全的?

      1. 在java程序中,常用的有两种机制来解决多线程并发问题,一种是sychronized方式,通过锁机制,一个线程执行时,让另一个线程等待,是以时间换空间的方式来让多线程串行执行。而另外一种方式就是ThreadLocal方式,通过创建线程局部变量,以空间换时间的方式来让多线程并行执行。两种方式各有优劣,适用于不同的场景,要根据不同的业务场景来进行选择。
      2. 在spring的源码中,就使用了ThreadLocal来管理连接,在很多开源项目中,都经常使用ThreadLocal来控制多线程并发问题,因为它足够的简单,我们不需要关心是否有线程安全问题,因为变量是每个线程所特有的。

      面试题: 很多人都说要慎用 ThreadLocal,谈谈你的理解,使用 ThreadLocal 需要注意些什么?

      ThreadLocal 变量解决了多线程环境下单个线程中变量的共享问题,使用名为ThreadLocalMap的哈希表进行维护(key为ThreadLocal变量名,value为ThreadLocal变量的值);

      使用时需要注意以下几点:

      线程之间的threadLocal变量是互不影响的,

      使用private final static进行修饰,防止多实例时内存的泄露问题

      线程池环境下使用后将threadLocal变量remove掉或设置成一个初始值


      2.Comparable比较器与Comparetor

      在现实生活中一个班级有很多个人数,也就意味着有许多个对象,如果经历一次考试之后我需要对考试后大家对象数组进行排序,需要成绩从高到低排序,比如黄小龙 成绩总分60 ,蓝小龙 成绩总分50,绿小龙 成绩总分 40等等,可以通过Comparable比较器去设置比较再进行排序

      Comparable接口常用的方法

      方法 描述
      int compareTo(T other) 将当前对象与另一个对象进行比较。返回一个负整数、零或正整数,表示当前对象小于、等于或大于另一个对象。
      boolean equals(Object obj) 检查当前对象是否与另一个对象相等。
      default Comparator<T> reversed() 返回当前比较器的逆序(降序)比较器。默认方法,可以在实现Comparable接口的类中使用。
      default Comparator<T> thenComparing(...) 返回一个比较器,该比较器首先使用当前比较器进行比较,然后使用其他指定的比较器进行进一步比较。默认方法,可以在实现Comparable接口的类中使用。

      注意:

      实现接口需要覆写CompareTo方法,该方法有三个返回值

        • return  1 -->表示结果大于
        • return -1-->表示结果小于
        • return  0 -->表示结果相等

        案例要求:经历一场考试后,班级需要对考试班里的同学排名的张贴告示,要求成绩从高到底,每个对象需要有以下信息:名字,学号,成绩总分。以下是本次班级考试结果

        张三,210101,560

        李四 ,  210108,   589

        王五 ,  210116,   340

        赵六,   210153,   623

        请用数组对象输出成绩从高到低

        案例代码:

        package Example1512;
        import org.jetbrains.annotations.NotNull;
        import java.util.Arrays;
        class Student implements Comparable<Student>{
            private int id;
            private String name;
            private int score;
            Student(String name,int id,int score){
                this.name = name;
                this.id = id;
                this.score = score;
            }
        //    覆写比较方法
            @Override
            public int compareTo(@NotNull Student o) {
                if (this.score<o.score){
                    return 1;
                }
                else if (this.score>o.score){
                    return -1;
                }else return 0;
            }
            @Override
            public String toString() {
                return "姓名:"+name + " 学号:" + id +" 总分:"+score;
            }
        }
        public class javaDemo {
            public static void main(String[] args) {
                Student[] allStudent= new Student[]{
                        new Student("张三",210101,560),
                        new Student("李四 ",210108,589),
                        new Student("王五 ",210116,340),
                        new Student("赵六",210153,623)
                };
                Arrays.sort(allStudent);
                System.out.println(Arrays.toString(allStudent));
            }
        }

        image.gif

        image.gif编辑

        注意:Arrays类中引用了Comparable的比较方法CompareTo才能用sort方法


        3.AutoCloseable接口

        由于资源是有限的,随着程序的扩大对资源越来越多的需求,资源会越来越紧张,所以为了保证资源的合理使用,一些资源使用后就不太需要的时候,就需要进行释放所以引入接口AutoCloseable进行自动的资源释放

        AutoCloseable接口的常用方法:

        方法 描述
        void close() throws Exception 关闭资源的方法,需要在实现类中实现。使用完资源后,应该调用该方法来释放资源。注意,close()方法可能会抛出异常,因此在调用时应该进行异常处理。
        default void addSuppressed(Throwable exception) 将一个异常附加到当前正在关闭的异常上。在关闭资源时,如果出现了其他异常,可以通过该方法将其添加到关闭资源的异常中,以便于统一处理。
        default void suppressedExceptions(Throwable... exceptions) 抑制指定的一组异常。在关闭资源时,如果出现了多个异常,可以通过该方法将它们全部抑制起来。

        案例要求与案例代码:

        package Example1513;
        interface Message extends AutoCloseable{
            public void send();
        }
        class NetMessage implements Message{
            private String Info;
        //    构造函数
            NetMessage(String Info){
                this.Info = Info;
            }
        //    判断是否正常建立连接
            public boolean open(){
                System.out.println("建立通信");
                return true;
            }
            @Override
            public void send() {
                if (open()){
                        if (Info.contains("马冬梅")){
                            throw new RuntimeException();
                        };
                    System.out.println("发送消息:"+Info);
                }
            }
            @Override
            public void close() throws Exception {
                System.out.println("自动关闭消息通道");
            }
        }
        public class javaDemo {
            public static void main(String[] args) {
                try (Message msg = new NetMessage("大爷");) {
                    msg.send();
                }catch (Exception e){
                    System.out.println("发送失败了捏");
                }
            }
        }

        image.gif

        image.gif编辑

        注意:

        要实现自动执行close()方法,可以使用try-with-resources语句块,它会在代码块结束后自动调用资源的close()方法来释放资源。

        public static void main(String[] args) {
            try (Message msg = new NetMessage("大爷")) {
                msg.send();
            } catch (Exception e) {
                System.out.println("发送失败了捏");
            }
        }

        image.gif


        4.(重点掌握)Optional空处理

        在引用对象中存在Null,但是如果调用一个类的静态方法,该方法实现需要一个实例化对象的值,那么此时会出现空异常(NullPointerException)

        举个例子

        String str = null;
        int length = str.length(); // 触发空指针异常,因为str对象未初始化

        image.gif

        为此Java提供Optional空处理来减少空指针异常的情况

        以下是Optional的常用方法

        方法 描述
        of(T value) 创建一个包含指定非空值的Optional对象
        ofNullable(T value) 创建一个包含指定值的Optional对象,如果值为空,则创建包含空值的Optional对象
        empty() 创建一个空的Optional对象
        isPresent() 判断Optional对象是否包含非空值
        get() 获取Optional对象中的非空值
        orElse(T other) 如果Optional对象中存在非空值,则返回该值;否则返回指定的默认值
        orElseGet(Supplier<? extends T> otherSupplier) 如果Optional对象中存在非空值,则返回该值;否则通过Supplier提供的方法生成并返回默认值
        orElseThrow(Supplier<? extends X> exceptionSupplier) 如果Optional对象中存在非空值,则返回该值;否则根据Supplier提供的方法抛出异常
        ifPresent(Consumer<? super T> consumer) 如果Optional对象中存在非空值,则对该值执行指定操作
        filter(Predicate<? super T> predicate) 如果Optional对象中存在非空值,并且满足指定条件,则返回当前对象;否则返回空的Optional对象
        map(Function<? super T,? extends U> mapper) 如果Optional对象中存在非空值,则对该值应用指定的映射函数,并返回包含映射结果的Optional对象
        flatMap(Function<? super T,Optional<U>> mapper) 如果Optional对象中存在非空值,则对该值应用指定的映射函数,并返回映射结果的Optional对象;否则返回空的Optional对象

        案例代码:

        package Example1515;
        import java.util.Optional;
        interface Message{
            public String getContent();
        }
        class MessageImp implements Message{
            @Override
            public String getContent() {
                return "输出获取到的非空对象信息";
            }
        }
        class MessageUtil{
        //    私立化构造方法防止外部调用
            private MessageUtil(){}
        //    获取非空对象
            public static Optional<Message> getMessageImp(){
                return Optional.of(new MessageImp());
            }
        //    获取内容
            public static void useMessage(Message msg){
                System.out.println(msg.getContent());
            }
        }
        public class javaDemo {
            public static void main(String[] args) {
                Optional<Message> test= MessageUtil.getMessageImp();
                if (test.isPresent()){
                    Message temp = test.get();
                    MessageUtil.useMessage(temp);
                }
            }
        }

        image.gif

        image.gif编辑


        目录
        相关文章
        |
        1月前
        |
        安全 架构师 Java
        Java大厂面试高频:Collection 和 Collections 到底咋回答?
        Java中的`Collection`和`Collections`是两个容易混淆的概念。`Collection`是集合框架的根接口,定义了集合的基本操作方法,如添加、删除等;而`Collections`是一个工具类,提供了操作集合的静态方法,如排序、查找、同步化等。简单来说,`Collection`关注数据结构,`Collections`则提供功能增强。通过小王的面试经历,我们可以更好地理解这两者的区别及其在实际开发中的应用。希望这篇文章能帮助你掌握这个经典面试题。
        44 4
        |
        3月前
        |
        监控 Java 应用服务中间件
        高级java面试---spring.factories文件的解析源码API机制
        【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
        130 2
        |
        26天前
        |
        Java 程序员
        Java社招面试中的高频考点:Callable、Future与FutureTask详解
        大家好,我是小米。本文主要讲解Java多线程编程中的三个重要概念:Callable、Future和FutureTask。它们在实际开发中帮助我们更灵活、高效地处理多线程任务,尤其适合社招面试场景。通过 Callable 可以定义有返回值且可能抛出异常的任务;Future 用于获取任务结果并提供取消和检查状态的功能;FutureTask 则结合了两者的优势,既可执行任务又可获取结果。掌握这些知识不仅能提升你的编程能力,还能让你在面试中脱颖而出。文中结合实例详细介绍了这三个概念的使用方法及其区别与联系。希望对大家有所帮助!
        163 60
        |
        2天前
        |
        Java 程序员 开发者
        Java社招面试题:一个线程运行时发生异常会怎样?
        大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
        33 14
        |
        5天前
        |
        安全 Java 程序员
        Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
        大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
        34 13
        |
        25天前
        |
        算法 安全 Java
        Java线程调度揭秘:从算法到策略,让你面试稳赢!
        在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
        65 16
        |
        22天前
        |
        Java 程序员 调度
        Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
        本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
        55 9
        |
        27天前
        |
        安全 Java 程序员
        Java面试必问!run() 和 start() 方法到底有啥区别?
        在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
        60 12
        |
        1月前
        |
        Java 程序员
        Java 排序神器:Comparable 和 Comparator 该怎么选?
        嗨,大家好,我是小米!今天和大家聊一聊Java社招面试中常考的经典问题——Comparable和Comparator的区别。Comparable定义对象的自然排序,适用于单一固定的排序规则;Comparator则是策略接口,用于定义自定义排序规则,适用于多样化或多变的排序需求。掌握这两者的区别是理解Java排序机制的基础,也是面试中的加分题。结合实际项目场景深入探讨它们的应用,能更好地打动面试官。如果你觉得有帮助,欢迎点赞、收藏、分享,期待你的一键三连!我们下期见~ 我是小米,一个喜欢分享技术的程序员,关注我的微信公众号“软件求生”,获取更多技术干货!
        46 20
        |
        1月前
        |
        监控 Dubbo Java
        Java Dubbo 面试题
        Java Dubbo相关基础面试题

        热门文章

        最新文章