You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

133 lines
5.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

通过学习JMM明白了主内存共享与工作内存ThreadLocal存在于工作内存中存在线程隔离特性其他线程无法访问。通俗点说就是线程的私有变量。
```JAVA
public class ThreadLocalTest02 {
public static void main(String[] args) {
ThreadLocal<String> local = new ThreadLocal<>();
IntStream.range(0, 10).forEach(i -> new Thread(() -> {
local.set(Thread.currentThread().getName() + ":" + i);
System.out.println("线程:" + Thread.currentThread().getName() + ",local:" + local.get());
}).start());
}
}
```
```text
输出结果:
线程Thread-0,local:Thread-0:0
线程Thread-1,local:Thread-1:1
线程Thread-2,local:Thread-2:2
线程Thread-3,local:Thread-3:3
线程Thread-4,local:Thread-4:4
线程Thread-5,local:Thread-5:5
线程Thread-6,local:Thread-6:6
线程Thread-7,local:Thread-7:7
线程Thread-8,local:Thread-8:8
线程Thread-9,local:Thread-9:9
```
从结果可以看到每一个线程都有自己的local 值这就是TheadLocal的基本使用 。
使用场景也是很丰富。
- 1、在进行对象跨层传递的时候使用ThreadLocal可以避免多次传递打破层次间的约束。
- 2、线程间数据隔离
- 3、进行事务操作用于存储线程事务信息。
- 4、数据库连接`Session`会话管理。
**ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景**
# 例
[这才是 Thread Local 的正确原理与适用场景 根本没有内存泄漏 | 技术世界 | java,thread local,java 8,CAS,多线程,并发,技术世界,郭俊 Jason (jasongj.com)](http://www.jasongj.com/java/threadlocal/)
对于 Java Web 应用而言Session 保存了很多信息。很多时候需要通过 Session 获取信息,有些时候又需要修改 Session 的信息。一方面,需要保证每个线程有自己单独的 Session 实例。另一方面,由于很多地方都需要操作 Session存在多方法共享 Session 的需求。如果不使用 ThreadLocal可以在每个线程内构建一个 Session实例并将该实例在多个方法间传递。
```java
public class SessionHandler {
@Data
public static class Session {
private String id;
private String user;
private String status;
}
public Session createSession() {
return new Session();
}
public String getUser(Session session) {
return session.getUser();
}
public String getStatus(Session session) {
return session.getStatus();
}
public void setStatus(Session session, String status) {
session.setStatus(status);
}
public static void main(String[] args) {
new Thread(() -> {
SessionHandler handler = new SessionHandler();
Session session = handler.createSession();
handler.getStatus(session);
handler.getUser(session);
handler.setStatus(session, "close");
handler.getStatus(session);
}).start();
}
}
```
该方法是可以实现需求的。但是每个需要使用 Session 的地方,都需要显式传递 Session 对象,方法间耦合度较高。
```java
public class SessionHandler {
public static ThreadLocal<Session> session = ThreadLocal.<Session>withInitial(() -> new Session());
@Data
public static class Session {
private String id;
private String user;
private String status;
}
public String getUser() {
return session.get().getUser();
}
public String getStatus() {
return session.get().getStatus();
}
public void setStatus(String status) {
session.get().setStatus(status);
}
public static void main(String[] args) {
new Thread(() -> {
SessionHandler handler = new SessionHandler();
handler.getStatus();
handler.getUser();
handler.setStatus("close");
handler.getStatus();
}).start();
}
}
```
使用 ThreadLocal 改造后的代码,不再需要在各个方法间传递 Session 对象,并且也非常轻松的保证了每个线程拥有自己独立的实例。
如果单看其中某一点替代方法很多。比如可通过在线程内创建局部变量可实现每个线程有自己的实例使用静态变量可实现变量在方法间的共享。但如果要同时满足变量在线程间的隔离与方法间的共享ThreadLocal再合适不过。
# 内存泄漏
ThreadLocal.ThreadLocalMap.Entry中的key是弱引用的也即是当某个ThreadLocal对象不存在强引用时就会被GC回收但是value是基于强引用的所以当key被回收但是value还存在其他强引用时就会出现内存的泄露情况在最新的ThreadLocal中已经做出了修改即在调用set、get、remove方法时会清除key为null的Entry但是如果不调用这些方法仍然还是会出现内存泄漏 :),所以要养成**用完ThreadLocal对象之后及时remove的习惯**。
# 总结
- ThreadLocal 并不解决线程间共享数据的问题
- ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
- 每个线程持有一个 Map 并维护了 ThreadLocal 对象与具体实例的映射,该 Map 由于只被持有它的线程访问,故不存在线程安全以及锁的问题
- ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为弱引用,避免了 ThreadLocal 对象无法被回收的问题
- ThreadLocalMap 的 set 方法通过调用 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏
- ThreadLocal 适用于变量在线程间隔离且在方法间共享的场景