栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Java

java 并发 - TreadLocal详解:它是怎么做到的线程隔离

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java 并发 - TreadLocal详解:它是怎么做到的线程隔离

文章目录
    • 1. ThreadLocal使用场景和理解
      • 1.1. 数据库连接管理
        • ThreadLocal登场
      • 1.2. ThreadLocal造成内存泄露的问题
      • 1.3. Session的管理
      • 1.4. java 开发手册中推荐的 ThreadLocal
      • 1.5. 每个线程维护了一个“序列号”
    • 2. ThreadLocal原理
      • 2.1. ThreadLocal如何实现的线程隔离

ThreadLocal会为每个线程创建单独的变量副本,避免因多线程操作共享变量导致数据不一致的现象。

1. ThreadLocal使用场景和理解 1.1. 数据库连接管理

看一个简单的数据库处理类,逻辑很简单一个是打开连接,一个是关闭连接。

class ConnectionManager {
    private static Connection connect = null;

    public static Connection openConnection() {
        if (connect == null) {
            connect = DriverManager.getConnection();
        }
        return connect;
    }

    public static void closeConnection() {
        if (connect != null)
            connect.close();
    }
}

我们回顾一下多线程下操作共享变量安全性的保证:

互斥同步:synchroniezd 和 reentrantLock
非阻塞同步:CAS、AtomicXXX
无同步方案:栈封闭、本地存储(Thread Local)。

对于上面的代码,因为有共享变量,多线程下可能会有安全问题。
安全问题:一个线程openConnection的同时,另一个变量在closeConnection;
同步问题:不互斥,有多个线程同时进入openConnection方法,多次创建了connect。

思考一个问题:需不需要互斥同步。
假设每个线程都有自己的connect变量,各个线程线程之间拿到connect之后,相互之间是没有依赖的。即一个线程修改了这个connect,不会影响其他线程对这个connect的使用。
 
因为每个线程使用的是对应CPU下的缓存,而不是内存中的数据。

但这会导致服务器压力过大,因为会在方法中频繁的创建数据连接。
 

ThreadLocal登场

ThreadLocal会在每个线程中对该变量创建一个副本,即每个线程都会有这样一个副本,互不影响。这样就不会造成线程安全和性能问题。

常用的例子

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

public class ConnectionManager {

    private static final ThreadLocal dbConnectionLocal = new ThreadLocal() {
        @Override
        protected Connection initialValue() {
            try {
                return DriverManager.getConnection("", "", "");
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
    };

    public Connection getConnection() {
        return dbConnectionLocal.get();
    }
}

注意下ThreadLocal的修饰符

如果我们希望通过某个类将状态(例如用户ID、事务ID)与线程关联起来,那么通常在这个类中定义private static类型的ThreadLocal 实例。

同时,threadLocal副本会造成内存的占用。

 

1.2. ThreadLocal造成内存泄露的问题

看下列代码:使用线程池来操作ThreadLocal。

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo {
    static class LocalVariable {
        private Long[] a = new Long[1024 * 1024];
    }

    // (1)
    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());
    // (2)
    final static ThreadLocal localVariable = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        // (3)
        Thread.sleep(5000 * 4);
        for (int i = 0; i < 50; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
                    // (4)
                    localVariable.set(new LocalVariable());
                    // (5)
                    System.out.println("use local varaible" + localVariable.get());
                    localVariable.remove();
                }
            });
        }
        // (6)
        System.out.println("pool execute over");
    }
}

如果使用线程池操作ThreadLocal会造成内存泄漏。

内存泄漏:指一个对象不使用了,但是一直占据着内存,具体的:对象不使用了,但是还被其他对象引用着,就会导致GC时不能回收此对象(根的可达性分析),这种情况就是内存泄漏。

因为对于线程池中没有销毁的线程,里面总是存在着 的强引用,而对于ThreadLocalMap 对于key虽然是弱引用,但是强引用不释放,弱引用就会一直有值,同时创建的Local Variable对象也不会释放,就造成了内存泄漏。

具体地,泄漏的内存 = coreThreadNum * LocalVariable对象的大小

ThreadLocal提供了一个清除线程中对象的方法, 即 remove。来避免内存泄漏的情况。

private void remove(ThreadLocal key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

 

1.3. Session的管理
private static final ThreadLocal threadSession = new ThreadLocal();  
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}  

 

1.4. java 开发手册中推荐的 ThreadLocal
import java.text.DateFormat;
import java.text.SimpleDateFormat;
 
public class DateUtils {
    public static final ThreadLocal df = new ThreadLocal(){
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };
}

调用

DateUtils.df.get().format(new Date());

 

1.5. 每个线程维护了一个“序列号”

当我们希望通过某个类将状态(例如用户ID、事务ID)与线程关联起来时,使用ThreadLocal。

为每一个线程维护一个序列号。

public class SerialNum {
    // The next serial number to be assigned
    private static int nextSerialNum = 0;

    private static ThreadLocal serialNum = new ThreadLocal() {
        protected synchronized Object initialValue() {
            return new Integer(nextSerialNum++);
        }
    };

    public static int get() {
        return ((Integer) (serialNum.get())).intValue();
    }
}

 
 

2. ThreadLocal原理 2.1. ThreadLocal如何实现的线程隔离

看一下set和get

先获取当前线程的map,然后set值,进行更新。如果这个线程没有map就先创建这个线程的map,并set值。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

获取当前线程的Map,如果不为空,则获取这个线程对应的值,否则初始化。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap threadLocals = getMap(t);
    if (threadLocals != null) {
        ThreadLocalMap.Entry e = threadLocals.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

***分析了get和set方法,对于ThreadLocal的线程隔离的实现其实很简单,就是为每个线程创建一个map,其中key存储着线程本身,value就是操作的共享变量。 ***

每个线程在get时,如果此线程没有set,则获取的是null。

 
 
 
参考:
https://pdai.tech/md/java/thread/java-thread-x-threadlocal.html

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1040184.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号