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(); } }
编辑
为什么要用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 是什么?有哪些使用场景?
- ThreadLocal 是一个本地线程副本变量工具类,在每个线程中都创建了一个 ThreadLocalMap 对象,简单说 ThreadLocal 就是一种以空间换时间的做法,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
- 原理:线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
- 经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
面试题:请谈谈 ThreadLocal 是怎么解决并发安全的?
- 在java程序中,常用的有两种机制来解决多线程并发问题,一种是sychronized方式,通过锁机制,一个线程执行时,让另一个线程等待,是以时间换空间的方式来让多线程串行执行。而另外一种方式就是ThreadLocal方式,通过创建线程局部变量,以空间换时间的方式来让多线程并行执行。两种方式各有优劣,适用于不同的场景,要根据不同的业务场景来进行选择。
- 在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)); } }
编辑
注意: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("发送失败了捏"); } } }
编辑
注意:
要实现自动执行
close()
方法,可以使用try-with-resources
语句块,它会在代码块结束后自动调用资源的close()
方法来释放资源。
public static void main(String[] args) { try (Message msg = new NetMessage("大爷")) { msg.send(); } catch (Exception e) { System.out.println("发送失败了捏"); } }
4.(重点掌握)Optional空处理
在引用对象中存在Null,但是如果调用一个类的静态方法,该方法实现需要一个实例化对象的值,那么此时会出现空异常(NullPointerException)
举个例子
String str = null; int length = str.length(); // 触发空指针异常,因为str对象未初始化
为此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); } } }
编辑