ThreadLocal 也叫线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。也就是说 ThreadLocal 可以为每个线程创建一个单独的变量副本,相当于线程的 private static 类型变量。
ThreadLocal 的作用和同步机制有些相反:同步机制是为了保证多线程环境下数据的一致性;而 ThreadLocal 是保证了多线程环境下数据的独立性。
实例:
public class ThreadLocalDemo {
private static String staticLabel;
private static ThreadLocal<String> threadLocalLabel = new ThreadLocal<>();
public static void main(String... args) {
staticLabel = "main";
threadLocalLabel.set("main");
Thread thread = new Thread() {
@Override
public void run() {
super.run();
staticLabel = "child";
threadLocalLabel.set("child");
}
};
thread.start();
try {
// 保证线程执行完毕
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("staticLabel = " + staticLabel);
System.out.println("threadLocalLabel = " + threadLocalLabel.get());
}
}
运行结果:
staticLabel = child
threadLocalLabel = main
ThreadLocal在很多框架中使用能够解决一些框架问题; 比如Spring中的事务、Spring 中作用域 Scope 为 Request的Bean 使用ThreadLocal来解决。
例如:设计DBConnectionFactory数据库工具类,保证每个线程获取到的Connection都是该线程独有的, 做到Connection的线程隔离; 所以并不存在线程安全问题。
//DBConnectionFactory.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBConnectionFactory {
private static final String driverClassName="com.mysql.cj.jdbc.Driver";
private static final String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false&allowPublicKeyRetrieval=true";
private static final String username="root";
private static final String password = "root";
//构造方法私有
private DBConnectionFactory(){
}
//加载驱动
static{
try{
Class.forName(driverClassName);
}catch(Exception ex){
ex.printStackTrace();
}
}
private static final ThreadLocal<Connection> dbConnectionLocal = new ThreadLocal<Connection>() {
//initialValue() 是 ThreadLocal 的初始值,默认返回 null,子类可以重写改方法,用于设置 ThreadLocal 的初始值。
@Override
protected Connection initialValue() {
try {
return DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
};
public static Connection getConnection() {
return dbConnectionLocal.get();
}
}
//DBConnectionFactoryDemo.java
import java.sql.Connection;
public class DBConnectionFactoryDemo {
public static void main(String[] args) {
Connection conn1 = DBConnectionFactory.getConnection();
Connection conn2 =DBConnectionFactory.getConnection();
System.out.println(conn1 == null);
System.out.println(conn2 == null);
System.out.println(conn1 == conn2);
}
}
运行结果:
false
false
true
这样测试结果显然是不正确的,因为conn1和conn2都是在main(主线程中)获得的,因此conn1和conn2指向同一个ThreadLocal上绑定的Connection对象。需要创建多个线程来测试。
改写如下:
import java.sql.Connection;
public class DBConnectionFactoryDemo {
public static Connection conn1;
public static Connection conn2;
public static void main(String[] args) {
Thread th1 = new Thread(() -> {
//super.run();
conn1 = DBConnectionFactory.getConnection();
});
Thread th2 = new Thread(() -> {
//super.run();
conn2 = DBConnectionFactory.getConnection();
});
th1.start();
th2.start();
try{
th1.join();
th2.join();
}catch(Exception ex){
ex.printStackTrace();
}
System.out.println(conn1 == null);
System.out.println(conn2 == null);
System.out.println(conn1 == conn2);
}
}
运行结果:
false
false
false