一、ThreadLocal简介
- ThreadLocal 叫做线程变量,意思是 ThreadLocal 中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
二、手写ThreadLocal模拟应用
1.MyThreadLocal
package com.bdg.threadlocal;
import java.util.HashMap;
import java.util.Map;
public class MyThreadLocal<T> {
private final Map<Thread, T> map = new HashMap<>();
public void set(T obj) {
map.put(Thread.currentThread(), obj);
}
public T get() {
return map.get(Thread.currentThread());
}
public void remove() {
map.remove(Thread.currentThread());
}
}
2.模拟连接对象Connection并编写工具类
(1)Connection
package com.bdg.threadlocal;
public class Connection {
}
(2)DBUtil
package com.bdg.threadlocal;
public class DBUtil {
private static final MyThreadLocal<Connection> local = new MyThreadLocal<>();
public static Connection getConnection() {
Connection conn = local.get();
if (conn == null) {
conn = new Connection();
local.set(conn);
}
return conn;
}
}
3.编写程序使用自己编写的MyThreadLocal
(1)UserDao
package com.bdg.threadlocal;
public class UserDao {
public void insert() {
Thread thread = Thread.currentThread();
System.out.println(thread);
Connection conn = DBUtil.getConnection();
System.out.println(conn);
System.out.println("User DAO insert");
}
}
(2)UserService
package com.bdg.threadlocal;
public class UserService {
private UserDao userDao = new UserDao();
public void save() {
Thread thread = Thread.currentThread();
System.out.println(thread);
Connection conn = DBUtil.getConnection();
System.out.println(conn);
userDao.insert();
}
}
(3)Test
package com.bdg.threadlocal;
public class Test {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread);
UserService userService = new UserService();
userService.save();
}
}
4.结果展示
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8cce557c40a1463790fe8a86316cb3dc.png)
三、ThreadLocal的应用场景
- ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景
- 1.存储用户 Session
- 2.数据库连接,处理数据库事务
public class DBUtil {
private static ResourceBundle bundle = ResourceBundle.getBundle("resources/jdbc");
private static String driver = bundle.getString("driver");
private static String url = bundle.getString("url");
private static String user = bundle.getString("user");
private static String password = bundle.getString("password");
private static ThreadLocal<Connection> local = new ThreadLocal<>();
private DBUtil() {
}
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection conn = local.get();
if (conn == null) {
conn = DriverManager.getConnection(url, user, password);
local.set(conn);
}
System.out.println(conn);
return conn;
}
public static void close (Connection conn, Statement stmt, ResultSet rs) {
if (conn != null) {
try {
conn.close();
local.remove();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
- 3.数据跨层传递(controller、service、dao)
- 上面手写 ThreadLocal 示例中处理了跨层访问的问题。
- 4.Spring 使用 ThreadLocal 解决线程安全问题
三、总结
- 通过上面的模拟我们可以看出,ThreadLocal 其实就是一个全局的大 Map,ThreadLocal 的 key 部分存储的是线程对象,我们可以将所有需要和当前线程绑定的数据都要放到这个容器中。
- 在 JDK 中给我们内置了 ThreadLocal ,不需要我们自己手动来写了,可以直接使用。
1.如何正确的使用ThreadLocal
- ① 将 ThreadLocal 变量定义成 private static 的,这样的话 ThreadLocal 的生命周期就更长,由于一直存在 ThreadLocal 的强引用,所以 ThreadLocal 也就不会被回收,也就能保证任何时候都能根据 ThreadLocal 的弱引用访问到 Entry 的 value 值,然后 remove 它,防止内存泄露
- ② 每次使用完 ThreadLocal,都调用它的 remove() 方法,清除数据。