• 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详解与应用

    《架构探险》—黄勇

  • 相关阅读:
    用Eclipse+MyEclipse开发struts的一个经典的实例(转)
    TOMCAT配置虚拟目录
    翻动100万级的数据(自定义的MSSQL分页查询过程)
    MyEclipse Hibernate 快速入门中文版
    微软提供的数据访问组件SqlHelper
    Java内存管理(一、内存分配)
    使用commonlogging与log4j打印日志,发现版本冲突
    Java内存管理(二、Java垃圾回收)
    初探java内存机制_堆和栈
    关于单CPU,多CPU上的原子操作
  • 原文地址:https://www.cnblogs.com/aheizi/p/7116484.html
Copyright © 2020-2023  润新知