Java多线程之ThreadLocal

ThreadLocal简介

ThreadLocal是Java中的线程局部变量,用于存放线程的局部变量。

ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但是确避免线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

首先看一下ThreadLocal的API:

  • get():返回此线程局部变量的当前线程副本中的值。
  • protected T initialValue(): 返回此线程局部变量的当前线程的“初始值”。
  • void remove(): 移除此线程局部变量当前线程的值。
  • void set(T value): 将此线程局部变量的当前线程副本中的值设置为指定值。

可以看出ThreadLocal内部应该就是封装了一个Map,自己实现ThreadLocal:

public class SimpleThreadLocal {
    private Map valueMap = Collections.synchronizedMap(new HashMap());
    public void set(Object newValue) {
        //①键为线程对象,值为本线程的变量副本
        valueMap.put(Thread.currentThread(), newValue);
    }
    public Object get() {
        Thread currentThread = Thread.currentThread();

        //②返回本线程对应的变量
        Object o = valueMap.get(currentThread); 
                
        //③如果在Map中不存在,放到Map中保存起来
        if (o == null && !valueMap.containsKey(currentThread)) {
            o = initialValue();
            valueMap.put(currentThread, o);
        }
        return o;
    }
    public void remove() {
        valueMap.remove(Thread.currentThread());
    }
    public Object initialValue() {
        return null;
    }
}

以上代码很好理解,JDK中的实现比这复杂,可以自行查看源码。

ThreadLocal基本使用

ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。

当一个类中使用了static成员变量的时候,一定要多问问自己,这个static成员变量需要考虑线程安全吗?也就是说,多个线程需要独享自己的static成员变量吗?如果需要考虑,不妨使用ThreadLocal。

例如,在单线程应用程序中可能会维护一个全局的数据库连接,并在程序启动的时候初始化这个连接,从而避免在调用每个方法的时候都要传递一个Connection对象。由于JDBC的连接对象不一定时线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就是不是线程安全的。通过把JDBC的连接保存到ThreadLocal对象中,每个线程都会拥有自己的连接。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
    @Override
    protected Connection initialValue() {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/test", "username",
                    "password");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
    };

    public static Connection getConnection() {
        return connectionHolder.get();
    }

    public static void setConnection(Connection conn) {
        connectionHolder.set(conn);
    }
}

ThreadLocal需要注意的点

只要使用了“池”(线程池、连接池),在使用ThreadLocal时,尤其需要注意,每个线程在使用ThreadLocal的时候,必须对ThreadLocal执行一次clear操作,避免出现线程污染问题。

线程池中的线程是重复利用的,只要线程还在,ThreadLocal线程本地变量会一直存在系统中,在JavaEE的服务器中尤为明显。

引用高广超在Java解读-ThreadLocal详解与应用中所说:

根据池中的线程数量(在运行环境中大于100个线程是正常的)以及ThreadLocal变量中对象的大小,可能会发生致命的内存问题。例如对线程池中的200个线程进行配置以及将ThreadLocal变量的大小设置为5MB,这将会导致有1GB的堆空间被这些变量所占用。这将会导致一个GC的开销并且可能会由于OutOfMemoryError导致JVM崩溃。

参考资料:

Java解读-ThreadLocal详解与应用

《架构探险》—黄勇

posted @ 2017-07-06 11:53  aheizi  阅读(465)  评论(0编辑  收藏