2024年Java最新Java-JUC-ThreadLocal(1),mybatis一级缓存和二级缓存面试题

结局:总结+分享

看完美团、字节、腾讯这三家的一二三面试问题,是不是感觉问的特别多,可能咱们真的又得开启面试造火箭、工作拧螺丝的模式去准备下一次的面试了。

开篇有提及我可是足足背下了Java互联网工程师面试1000题,多少还是有点用的呢,换汤不换药,不管面试官怎么问你,抓住本质即可!能读到此处的都是真爱

  • Java互联网工程师面试1000题

image.png

而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的 《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题。

  • 程序员代码面试指南–IT名企算法与数据结构题目最优解

image.png

  • 其余像设计模式,建议可以看看下面这4份PDF(已经整理)

image.png

  • 更多的Java面试学习笔记如下,关于面试这一块,我额外细分出Java基础-中级-高级开发的面试+解析,以及调优笔记等等等。。。

image.png

以上所提及的全部Java面试学习的PDF及笔记,如若皆是你所需要的,那么都可发送给你!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

static ThreadLocal tl = new ThreadLocal<>();

public static void main(String[] args) {

new Thread(() -> {

try {

TimeUnit.SECONDS.sleep(2);

} catch (InterruptedException e) {

e.printStackTrace();

}

//null

System.out.println(tl.get());

}).start();

new Thread(() -> {

try {

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

tl.set(new Person());

//com.java.threadlocal.Person@4ebf4ac7

System.out.println(tl.get());

}).start();

}

各部分关系


  • 一个Thread有一个ThreadLocalMap

  • 一个ThreadLocalMap包含多个Entry对

  • 一个Entry对为一个ThreadLocal对象和value构成

  • 一个ThreadLocal可以作为多个ThreadThreadLocalMapkey

  • 一个Thread只能通过自己的ThreadLocalMap,根据ThreadLocal获取对应的value

  • JDK8后,这种设计方式每个ThreadLocalMap存储的键值对少,每个Thread维护自己的ThreadLocalMap,一个ThreadLocalMap的键值对数量由ThreadLocal决定,而实际开发中,并不是很多,避免哈希冲突

JDK8之间,由ThreadLocal维护一个MapThread-value作为键值对,个数由线程决定

  • Thread销毁之后,ThreadLocalMap也会随之销毁,减少内存使用

和sychronized区别


  • 共同点,都能用于处理多线程并发访问变量的问题

  • sychronized时间换空间,只提供一份变量,让不同线程排队访问,侧重点在于多个线程访问资源的同步

  • ThreadLocal空间换时间,为每个线程都提供一个线程独享的变量,实现同时访问而不互相干扰,侧重点在于每个线程之间的数据隔离

spring事务中的应用


  • 保证所有操作都在一个事务中,每个操作使用的连接都必须是同一个

数据层和服务层的connection是同一个

  • 线程并发的情况下,每个线程只能操作各自的connection

  • 普通解决方案,需要将连接作为参数传入,并且要用synchronized保证线程安全

增加代码耦合度,影响性能

源码实例

  • @Transactional最终调用DataSourceTransactionManager,利用ThreadLocal传递connection

  • doBegin首先检查是否有连接对象,没有则获取一个,并且会设置给newConnectionHolder

  • doBegin会检查是否是新的连接,如果是将新连接通过TransactionSynchronizationManagerThreadLocalMap绑定

  • 设置给resources,以map类型存储,key是数据源,value为连接,说明一个线程,对应的一个数据源,对应一个连接

  • resources实际上就是个ThreadLocal,里面的元素类型为Map<Object, Object>

在MyBatis的应用


  • 关于分页PageHelper,会根据当前数据库连接,选择合适的分页方式

PageHelper.startPage(2, 1);

List accounts = accountMapper.findAll();

for (Account account : accounts) {

System.out.println(account);

  • startPage

public static Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {

Page page = new Page(pageNum, pageSize, count);

page.setReasonable(reasonable);

page.setPageSizeZero(pageSizeZero);

//当执行过orderBy的时候

Page oldPage = getLocalPage();

if (oldPage != null && oldPage.isOrderByOnly()) {

page.setOrderBy(oldPage.getOrderBy());

}

//关键

setLocalPage(page);

return page;

}

  • setLocalPage,给ThreadLocalMap设置Page对象

protected static void setLocalPage(Page page) {

LOCAL_PAGE.set(page);

}

  • PageInterceptor拦截器中,调用intercept,完成所有操作调用afterAll

  • afterAll,将当前线程对应的dialectpage清理,remove

public void afterAll() {

AbstractHelperDialect delegate = this.autoDialect.getDelegate();

if (delegate != null) {

delegate.afterAll();

this.autoDialect.clearDelegate();

}

clearPage();

}

  • getDelegate实际上也是从ThreadLocal获取当前线程的AbstractHelperDialect,应用了代理模式,最终由PageHelper来增强删除

public AbstractHelperDialect getDelegate() {

return this.delegate != null ? this.delegate : (AbstractHelperDialect)this.dialectThreadLocal.get();

}

  • clearPage调用remove

public static void clearPage() {

LOCAL_PAGE.remove();

}

Set


  • 主要工作
  1. 设置值,如果没有ThradLocalMap就为其创建
  1. 在实际设置的过程中,如果找到k相等的,就替换;如果找到k==null,就进行一次清理工作,并在清理同时,如果找到k相等的,同样替换,如果没有相等的,就放找到为null的地方
  • 获取到当前线程,放value是放入当前线程对于的map里,mapkey为当前ThreadLocal对象

public void set(T value) {

//当前线程

Thread t = Thread.currentThread();

//获取map

ThreadLocalMap map = getMap(t);

if (map != null)

//key - 当前ThreadLocal的实例对象

map.set(this, value);

else

//没有则创建

createMap(t, value);

}

  • getMap对应的为Thread的成员变量threadLocals,每出现一个线程,就会初始化一个ThreadLocalMap类型的threadLocals,专属于的该线程的map

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

  • createMap 初始化当前线程对应的ThreadLocalMap

void createMap(Thread t, T firstValue) {

//当前ThreadLocal的实例对象作为key

t.threadLocals = new ThreadLocalMap(this, firstValue);

}

  • ThreadLocalMap构造

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {

//table 为 Entry(继承了WeakReference)数组 INITIAL_CAPACITY 默认 16

//容量必须是2的整数次幂

table = new Entry[INITIAL_CAPACITY];

//线性探测法,找到一个下标

int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

table[i] = new Entry(firstKey, firstValue);

size = 1;

//设置扩容阈值,当大于它时,需要扩容

setThreshold(INITIAL_CAPACITY);

}

  • set,实际进行set的操作,在当前线程对应的map中遍历

private void set(ThreadLocal<?> key, Object value) {

Entry[] tab = table;

int len = tab.length;

int i = key.threadLocalHashCode & (len-1);

//遍历所有的Entry 线性探测法

for (Entry e = tab[i];

e != null;

//实际上是循环遍历 i + 1 < len ? i + 1 : 0

e = tab[i = nextIndex(i, len)]) {

ThreadLocal<?> k = e.get();

if (k == key) { //相等则替换 不相等,就找下一个,此时hash冲突了

e.value = value;

return;

}

if (k == null) {

//发现一个为null的key

//1.第一次遍历做一次整体的清理,并保存第一个为null的地方,防止后续突然增加大量数据

//2.第二次遍历找跟当前key是否有相等的,有或没有都放到i的位置,原先的位置置null

//3.清理所有entry指向null的下标

replaceStaleEntry(key, value, i);

return;

}

}

tab[i] = new Entry(key, value);

int sz = ++size;

//清理为null的元素

if (!cleanSomeSlots(i, sz) && sz >= threshold)

rehash();

}

get


  • 主要工作
  1. 获取ThreadLocal实例对象对应的值,如果没有就返回null
  1. 如果当前Thread没有ThreadLocalMap为其创建,并将ThreadLocal-null加入
  1. 搜索时,第一次尝试直接命中,如果找不到,尝试遍历搜索,同时清理k == nullEntry
  • get,范型写法

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

//根据ThreadLocal实例对象获得Entry

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

//抑制没被使用的警告

@SuppressWarnings(“unchecked”)

//强转

T result = (T)e.value;

return result;

}

}

//当前线程没有对应的map 或者 没有找到当前key对应的value 返回null

return setInitialValue();

}

  • getEntry

private Entry getEntry(ThreadLocal<?> key) {

int i = key.threadLocalHashCode & (table.length - 1);

Entry e = table[i];

//看看能不能正好找到

if (e != null && e.get() == key)

return e;

else //找不到就遍历

return getEntryAfterMiss(key, i, e);

}

  • getEntryAfterMiss,遍历搜索,在遍历的同时,清除为nullEntry

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {

Entry[] tab = table;

int len = tab.length;

while (e != null) { //没找到 返回null

ThreadLocal<?> k = e.get();

if (k == key)

return e;

if (k == null) //找到为null的,直接清除

expungeStaleEntry(i);

else

i = nextIndex(i, len);

e = tab[i];

}

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

Entry[] tab = table;

int len = tab.length;

while (e != null) { //没找到 返回null

ThreadLocal<?> k = e.get();

if (k == key)

return e;

if (k == null) //找到为null的,直接清除

expungeStaleEntry(i);

else

i = nextIndex(i, len);

e = tab[i];

}

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

[外链图片转存中…(img-ukjOk9jJ-1714911358385)]

[外链图片转存中…(img-Ru5XZXk1-1714911358386)]

[外链图片转存中…(img-LuEJ7WWI-1714911358386)]

[外链图片转存中…(img-rCL1fpqP-1714911358386)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值