public class ConnectionHelper {
private ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
try {
Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
String DB_URL = "jdbc:mysql://localhost:3306/database";
return DriverManager.getConnection(DB_URL, "root", "root");
} catch (SQLException e) {
throw new RuntimeException("Unable to acquire Connection, e");
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
};
public Connection getConnection() {
return connectionHolder.get();
}
}
上述我们创建了 ThreadLocal<Connection> 的一个泛型实例,然后用下述验证:
public class Test {
static ConnectionHelper connectionHelper = new ConnectionHelper();
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(connectionHelper.getConnection());
}
}
public static void main(String[] args) {
Thread t1 = new MyThread();
Thread t2 = new MyThread();
t1.start();
t2.start();
}
}
线程的封闭性
当多线程共享可变的数据时,通常需要使用同步来保证并发安全。如果仅在单线程内访问数据,就不需要同步,这就是线程的封闭性。
栈封闭
其实我们在写 Web 程序的时候,用到很多的就是栈封闭。举例如下:
如上所示,
fruit
实例被封闭进方法中,多线程环境下并不会影响fruit
。ThreadLocal
ThreadLocal 对象通常用于防止对“可变的单实例变量(Singleton)”或全局变量进行共享。使用场景通常为:当每个线程都需要分配一个临时对象,并且希望对象没有同步问题,则可以用 ThreadLocal 类
如:在数据库查询的时候,多个查询对应于多个线程,每个线程执行查询需要 Connection 对象。由于 JDBC 的 Connection 不是线程安全的,因此将其保存到 ThreadLocal 中让每个线程都有各自的 Connection 对象。
上述我们创建了
ThreadLocal<Connection>
的一个泛型实例,然后用下述验证:可以发现,t1 和 t2 打印出来的 Connection 实例不为 NULL 也不相同。
即为每一个线程创建了一个 Connection 对象
ThreadLocal 原理分析
当某个线程初次调用 ThreadLocal.get() 方法时,会首先调用 initValue 来获取初始值。
我们点击上述的
connectionHolder.get()
方法从上述可以看出来,我们的数据是存储在了线程的一个变量里,所以其是线程安全的。
这里需要说的是
线程池中使用 ThreadLocal
由于我们存储的 ThreadLocal 生命周期是随着线程结束而结束的,在线程池中会出现这种情况:
线程永远不会结束,则 ThreadLocal 对应线程的数据越来越多,并且一直被引用不会被 GC,会造成内存泄露的情况。
// TODO
关于这块回头再补充下示例