结局:总结+分享
看完美团、字节、腾讯这三家的一二三面试问题,是不是感觉问的特别多,可能咱们真的又得开启面试造火箭、工作拧螺丝的模式去准备下一次的面试了。
开篇有提及我可是足足背下了Java互联网工程师面试1000题,多少还是有点用的呢,换汤不换药,不管面试官怎么问你,抓住本质即可!能读到此处的都是真爱
- Java互联网工程师面试1000题
而且从上面三家来看,算法与数据结构是必备不可少的呀,因此我建议大家可以去刷刷这本左程云大佬著作的 《程序员代码面试指南 IT名企算法与数据结构题目最优解》,里面近200道真实出现过的经典代码面试题。
- 程序员代码面试指南–IT名企算法与数据结构题目最优解
- 其余像设计模式,建议可以看看下面这4份PDF(已经整理)
- 更多的Java面试学习笔记如下,关于面试这一块,我额外细分出Java基础-中级-高级开发的面试+解析,以及调优笔记等等等。。。
以上所提及的全部Java面试学习的PDF及笔记,如若皆是你所需要的,那么都可发送给你!
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
可以作为多个Thread
的ThreadLocalMap
的key
-
一个
Thread
只能通过自己的ThreadLocalMap
,根据ThreadLocal
获取对应的value
- JDK8后,这种设计方式每个
ThreadLocalMap
存储的键值对少,每个Thread
维护自己的ThreadLocalMap
,一个ThreadLocalMap
的键值对数量由ThreadLocal
决定,而实际开发中,并不是很多,避免哈希冲突
JDK8之间,由
ThreadLocal
维护一个Map
,Thread-value
作为键值对,个数由线程决定
- 当
Thread
销毁之后,ThreadLocalMap
也会随之销毁,减少内存使用
和sychronized区别
-
共同点,都能用于处理多线程并发访问变量的问题
-
sychronized
时间换空间,只提供一份变量,让不同线程排队访问,侧重点在于多个线程访问资源的同步
-
ThreadLocal
空间换时间,为每个线程都提供一个线程独享的变量,实现同时访问而不互相干扰,侧重点在于每个线程之间的数据隔离
spring事务中的应用
- 保证所有操作都在一个事务中,每个操作使用的连接都必须是同一个
数据层和服务层的
connection
是同一个
-
线程并发的情况下,每个线程只能操作各自的connection
-
普通解决方案,需要将连接作为参数传入,并且要用
synchronized
保证线程安全
增加代码耦合度,影响性能
源码实例
-
@Transactional
最终调用DataSourceTransactionManager
,利用ThreadLocal
传递connection
-
doBegin
首先检查是否有连接对象,没有则获取一个,并且会设置给newConnectionHolder
doBegin
会检查是否是新的连接,如果是将新连接通过TransactionSynchronizationManager
与ThreadLocalMap
绑定
- 设置给
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
,将当前线程对应的dialect
和page
清理,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
- 主要工作
- 设置值,如果没有
ThradLocalMap
就为其创建
- 在实际设置的过程中,如果找到
k
相等的,就替换;如果找到k==null
,就进行一次清理工作,并在清理同时,如果找到k
相等的,同样替换,如果没有相等的,就放找到为null
的地方
- 获取到当前线程,放
value
是放入当前线程对于的map
里,map
的key
为当前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
- 主要工作
- 获取
ThreadLocal
实例对象对应的值,如果没有就返回null
- 如果当前
Thread
没有ThreadLocalMap
为其创建,并将ThreadLocal-null
加入
- 搜索时,第一次尝试直接命中,如果找不到,尝试遍历搜索,同时清理
k == null
的Entry
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
,遍历搜索,在遍历的同时,清除为null
的Entry
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];
}
最后
各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了
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)]