先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
| ThreadLocal() | 创建ThreadLocal对象 |
| public void set( T value) | 设置当前线程绑定的局部变量 |
| public T get() | 获取当前线程绑定的局部变量 |
| public void remove() | 移除当前线程绑定的局部变量 |
2.2 栗子
我们先来看一个案例:
/**
-
@author xppll
-
@date 2021/12/25 21:23
*/
public class MyDemo1 {
private String content;
private String getContent() {
return content;
}
private void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
MyDemo1 demo = new MyDemo1();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
demo.setContent(Thread.currentThread().getName() + “的数据”);
System.out.println(“----------------------”);
System.out.println(Thread.currentThread().getName() + “—>” + demo.getContent());
});
thread.setName(“线程” + i);
thread.start();
}
}
}
其中一次结果:
从结果可以看出多个线程在访问同一个变量的时候出现的异常,线程间的数据没有隔离。下面我们来看下采用 ThreadLocal
的方式来解决这个问题:
/**
-
@author xppll
-
@date 2021/12/25 21:23
*/
public class MyDemo1 {
ThreadLocal t1 = new ThreadLocal<>();
private String content;
private String getContent() {
//获取当前线程绑定的变量
return t1.get();
}
private void setContent(String content) {
// this.content = content;
//变量content绑定到当前线程
t1.set(content);
}
public static void main(String[] args) {
MyDemo1 demo = new MyDemo1();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
demo.setContent(Thread.currentThread().getName() + “的数据”);
System.out.println(“----------------------”);
System.out.println(Thread.currentThread().getName() + “—>” + demo.getContent());
});
thread.setName(“线程” + i);
thread.start();
}
}
}
结果:
从结果来看,这样很好的解决了多线程之间数据隔离的问题,十分方便
2.3 ThreadLocal类与synchronized关键字
这里可能有的朋友会觉得在上述例子中我们完全可以通过加锁来实现这个功能。我们首先来看一下用synchronized
代码块实现的效果:
/**
-
@author xppll
-
@date 2021/12/25 21:23
*/
public class MyDemo1 {
private String content;
private String getContent() {
return content;
}
private void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
MyDemo1 demo = new MyDemo1();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(() -> {
synchronized (MyDemo1.class) {
demo.setContent(Thread.currentThread().getName() + “的数据”);
System.out.println(“----------------------”);
System.out.println(Thread.currentThread().getName() + “—>” + demo.getContent());
}
});
thread.setName(“线程” + i);
thread.start();
}
}
}
结果:
从结果可以发现,加锁确实可以解决这个问题,但是在这里我们强调的是线程数据隔离的问题,并不是多线程共享数据的问题, 在这个案例中使用synchronized
关键字是不合适的。而且这里使用synchronized
性能会更低。使用ThreadLocal
有更高的并发性。
两者区别:
| | synchronized | ThreadLocal |
| — | — | — |
| 原理 | 同步机制采用以时间换空间的方式,只提供了一份变量,让不同的线程排队访问 | ThreadLocal采用以空间换时间的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰 |
| 侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
3.1 JDK8之前的设计
如果我们不去看源代码的话,可能会猜测ThreadLocal
是这样子设计的:每个ThreadLocal
都创建一个Map
,然后用线程作为Map
的key
,要存储的局部变量作为Map
的value
,这样就能达到各个线程的局部变量隔离的效果。这是最简单的设计方法,JDK最早期的ThreadLocal
确实是这样设计的,但现在早已不是了。
3.2 JDK8的优化
JDK后面优化了设计方案,在JDK8中 ThreadLocal
的设计是:每个Thread
维护一个ThreadLocalMap
,这个Map的key
是ThreadLocal
实例本身,value
才是真正要存储的值Object
。
具体的过程是这样的:
-
每个Thread线程内部都有一个Map (ThreadLocalMap)
-
Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
-
Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
-
对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
这样设计的好处:
-
这样设计之后每个Map存储的Entry数量就会变少。因为之前的存储数量由Thread的数量决定,现在是由ThreadLocal的数量决定。在实际运用当中,往往ThreadLocal的数量要少于Thread的数量。
-
当Thread销毁之后,对应的ThreadLocalMap也会随之销毁,能减少内存的使用。
基于ThreadLocal
的内部结构,我们继续分析它的核心方法源码,更深入的了解其操作原理。
除了构造方法之外,ThreadLocal
对外暴露的方法有以下4个:、
| 方法声明 | 描述 |
| — | — |
| protected T initialValue() | 返回当前线程局部变量的初始值 |
| public void set( T value) | 设置当前线程绑定的局部变量 |
| public T get() | 获取当前线程绑定的局部变量 |
| public void remove() | 移除当前线程绑定的局部变量 |
接下来对四个方法进行源码解析
4.1 set方法
对应源码:
/**
-
设置当前线程对应的ThreadLocal的值
-
@param value 将要保存在当前线程对应的ThreadLocal的值
*/
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
}
/**
-
获取当前线程Thread对应维护的ThreadLocalMap
-
@param t the current thread 当前线程
-
@return the map 对应维护的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
-
创建当前线程Thread对应维护的ThreadLocalMap
-
@param t 当前线程
-
@param firstValue 存放到map中第一个entry的值
*/
void createMap(Thread t, T firstValue) {
//这里的this是调用此方法的threadLocal
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
整体可以分为几步:
-
首先获取当前线程,并根据当前线程获取一个Map
-
如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)
-
如果Map为空,则给该线程创建 Map,并设置初始值
4.2 get方法
对应源码:
/**
- 返回当前线程中保存ThreadLocal的值
*/
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果此map存在
if (map != null) {
// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
ThreadLocalMap.Entry e = map.getEntry(this);
// 对e进行判空
if (e != null) {
@SuppressWarnings(“unchecked”)
// 获取存储实体 e 对应的 value值
// 即为我们想要的当前线程对应此ThreadLocal的值
T result = (T)e.value;
return result;
}
}
/*
初始化 : 有两种情况有执行当前代码
第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象
第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry
*/
return setInitialValue();
}
/**
-
初始化
-
@return the initial value 初始化后的值
*/
private T setInitialValue() {
// 调用initialValue获取初始化的值
// 此方法可以被子类重写, 如果不重写默认返回null
T value = initialValue();
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
// 返回设置的值value
return value;
}
整体可以分为几步:
-
首先获取当前线程, 根据当前线程获取一个Map
-
如果获取的
Map
不为空,则在Map中以ThreadLocal
的引用作为key来在Map中获取对应的Entry e,否则转到4 -
如果
e
不为null,则返回e.value,否则转到4 -
Map为空或者e为空,则通过
initialValue
函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map
总结成一句话就是:先获取当前线程的 ThreadLocalMap 变量,如果存在则返回值,不存在则创建并返回初始值。
4.3 remove方法
对应源码:
/**
- 删除当前线程中保存的ThreadLocal对应的实体entry
*/
public void remove() {
// 获取当前线程对象中维护的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
// 如果此map存在
if (m != null)
// 存在则调用map.remove
// 以当前ThreadLocal为key删除对应的实体entry
m.remove(this);
}
整体可以分为几步:
-
首先获取当前线程,并根据当前线程获取一个Map
-
如果获取的Map不为空,则移除当前ThreadLocal对象对应的entry
4.4 initialValue方法
对应源码:
/**
-
返回当前线程对应的ThreadLocal的初始值
-
此方法的第一次调用发生在,当线程通过get方法访问此线程的ThreadLocal值时
-
除非线程先调用了set方法,在这种情况下,initialValue 才不会被这个线程调用。
-
通常情况下,每个线程最多调用一次这个方法。
-
这个方法仅仅简单的返回null {@code null};
-
如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值,
-
必须通过子类继承{@code ThreadLocal} 的方式去重写此方法
-
通常, 可以通过匿名内部类的方式实现
-
@return 当前ThreadLocal的初始值
*/
protected T initialValue() {
return null;
}
此方法的作用:返回该线程局部变量的初始值。
(1) 这个方法是一个延迟调用方法,从上面的代码我们得知,在set方法还未调用而先调用了get方法时才执行,并且仅执行1次
(2)这个方法缺省实现直接返回一个null
(3)如果想要一个除null之外的初始值,可以重写此方法。(备注: 该方法是一个protected的方法,显然是为了让子类覆盖而设计的)
5.1 基本结构
ThreadLocalMap
是ThreadLocal
的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现。如下图
成员变量
/**
- 初始容量 —— 必须是2的整次幂
*/
private static final int INITIAL_CAPACITY = 16;
/**
-
存放数据的table,Entry类的定义在下面分析
-
同样,数组长度必须是2的整次幂。
*/
private Entry[] table;
/**
- 数组里面entrys的个数,可以用于判断table当前使用量是否超过阈值。
*/
private int size = 0;
/**
- 进行扩容的阈值,表使用量大于它的时候进行扩容。
*/
private int threshold; // Default to 0
跟HashMap类似:
-
INITIAL_CAPACITY
代表这个Map的初始容量 -
table
是一个Entry
类型的数组,用于存储数据 -
size
代表表中的存储数目 -
threshold
代表需要扩容时对应 size 的阈值
存储结构 - Entry
/*
-
Entry继承WeakReference,并且用ThreadLocal作为key
-
如果key为null(entry.get() == null),意味着key不再被引用
-
因此这时候entry也可以从table中清除
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
从源码就可以看出:
-
在
ThreadLocalMap
中,也是用Entry
来保存K-V
结构数据的。不过Entry
中的key
只能是ThreadLocal
对象,这点在构造方法中已经限定死了 -
另外,
Entry
继承WeakReference
,也就是key
(ThreadLocal)是弱引用,其目的是将ThreadLocal
对象的生命周期和线程生命周期解绑
5.2 弱引用和内存泄漏
先来了解一些概念:
内存相关概念
总结
对于面试,一定要有良好的心态,这位小伙伴面试美团的时候没有被前面阿里的面试影响到,发挥也很正常,也就能顺利拿下美团的offer。
小编还整理了大厂java程序员面试涉及到的绝大部分面试题及答案,希望能帮助到大家,
最后感谢大家的支持,希望小编整理的资料能够帮助到大家!也祝愿大家都能够升职加薪!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
adLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
从源码就可以看出:
-
在
ThreadLocalMap
中,也是用Entry
来保存K-V
结构数据的。不过Entry
中的key
只能是ThreadLocal
对象,这点在构造方法中已经限定死了 -
另外,
Entry
继承WeakReference
,也就是key
(ThreadLocal)是弱引用,其目的是将ThreadLocal
对象的生命周期和线程生命周期解绑
5.2 弱引用和内存泄漏
先来了解一些概念:
内存相关概念
总结
对于面试,一定要有良好的心态,这位小伙伴面试美团的时候没有被前面阿里的面试影响到,发挥也很正常,也就能顺利拿下美团的offer。
小编还整理了大厂java程序员面试涉及到的绝大部分面试题及答案,希望能帮助到大家,
[外链图片转存中…(img-dh2HtsLt-1713434153863)]
[外链图片转存中…(img-YJ0pWrYW-1713434153863)]
最后感谢大家的支持,希望小编整理的资料能够帮助到大家!也祝愿大家都能够升职加薪!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-Mb8h93Nr-1713434153864)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!