← 返回首页
JavaSE基础教程(一百零五)
发表时间:2022-04-14 22:12:29
ThreadLocal

1.什么是ThreadLocal

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

2.ThreadLocal使用场景

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