首页 > 其他分享 >threadLocal

threadLocal

时间:2022-10-25 01:55:25浏览次数:35  
标签:Thread ThreadLocalMap ThreadLocal value threadLocal 线程 public

1. ThreadLocal辨析

ThreadLocal与Synchronize的比较

ThreadLocal和Synchronize都用于解决多线程并发访问,但是ThreadLocal与Synchronize有本质的区别。

  • Synchronized是利用锁的机制,使变量或代码块在某一时刻仅仅能被一个线程访问。

  • ThreadLocal则是为每个线程都提供了一个变量的副本,使得每个线程在某一时刻访问到的并非是同一个对象,这样就隔离了多个线程对数据的共享。

2. ThreadLocal的应用场景

Spring的事务就借助了ThreadLocal类。

Spring会从数据库连接池中获得一个Connection,然后会把Connection放到ThreadLocal中,也就和线程绑定了,事务需要提交或回滚的时候,只需要从ThreadLocal中拿到Connection进行操作

3.ThreadLocal的简单使用

ThreadLocal有四个方法

  1. void set(Object value)

    设置当前线程的线程局部变量的值

  2. public Object get()

    该方法返回当前线程所对应的线程局部变量

  3. public void remove()

    将当前线程句柄变量的值删除,目的是为了减少内存的占用。该方法是JDK5.0新增的方法。需要指出的是,当线程结束后,对应线程的局部变量将自动被垃圾回收,所以显示调用该方法清楚线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

  4. protected Object initialValue()

    返回该线程句柄变量的初始值,该方法是一个Protected方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第一次调用get()或set(Object)时才执行,并且只执行一次。ThreadLocal中的缺省实现是直接返回一个null。

public final static ThreadLocal<String> resource = new ThreadLocal<String>;

​ resource代表一个能存放String类型的ThreadLocal对象。此时不论什么一个线程都能够并发访问这个变量,对它进行写入,读取操作,都是线程安全的。

4. 实现解析

1666621127980


public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.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;
} 


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);
}

  • 调用ThreadLocal的set方法的时候,会获取当前线程,根据当前线程调用getMap(t)方法,第一次是为空的,调用createMap(t,value)方法;创建一个ThreadLocalMap对象,以ThreadLocal为key,传入进来的value为值。再次set的时候会覆盖
  • 调用ThreadLocal的get方法的时候。会根据当前的线程找到对应的ThreadLocalMap,获取对应key为ThreadLocal的Map
  • ThreadLocalMap里面是一个Entry对象,和Map的Entry类似,是一个K,V键值对。key为ThreadLocal对象,V为传进来的value,但是key为弱引用,当发生垃圾回收的时候,如果内存不足的话,key会被回收

所以,我们调用ThreadLocal的get方法,其实就是拿到当前线程对应的ThreadLocalMap对象,根据ThreadLocal为key找到对应的值

5. ThreadLocal内存泄漏分析

内存泄漏:

不再使用的对象无法被垃圾回收器回收

package com.wxc.thread.threadlocal;

import lombok.extern.slf4j.Slf4j;

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

/**
 * ThreadLocal造成内存泄漏演示
 */
@Slf4j
public class ThreadLocalOOM {
    private static final int TASK_LOOP_SIZE = 500;

    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5,
                                                                          5,
                                                                          1,
                                                                          TimeUnit.MINUTES,
                                                                          new LinkedBlockingDeque<>());

    static class LocalVariable {
        private byte[] bytes = new byte[1024 * 1024 * 5];
    }

    static ThreadLocal<LocalVariable> threadLocal = new ThreadLocal<LocalVariable>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < TASK_LOOP_SIZE; i++) {
            poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    threadLocal.set(new LocalVariable());
                    //                    new LocalVariable();
                    log.info("use local variable");
                    //                    threadLocal.remove();
                }
            });

            Thread.sleep(1000);
        }

        log.info("pool execute over...");
    }
}

1666623455299

每个Thread都维护一个ThreadLocalMap,这个映射表的key就是ThreadLocal本身,value是真正要存储的值,也就是说ThreadLocal本身并不存储值。它只是作为一个key来让线程从ThreadLocalMap获取Value。仔细观察ThreadLocalMap,这个Map是使用ThreadLocal的弱引用作为key的,弱引用的对象在GC时会被回收。因此,使用了ThreadLocal之后,引用链如下:

1666623515649

图中的虚线表示弱引用。

这样,当把threadLocal变量置为null以后,没有任何强引用指向ThreadLocal实例,所以ThreadLocal将会被gc回收。这样一来,ThreadLocalMap就会出现key为null的Entry,就没有办法访问这些key为null的value,如果当前线程迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Current Thread Ref -> Current Thread->ThreadLocalMap->Entry->Value,而这块value永远不会被访问到了,所以存在内存泄漏。

只有当前Thrad结束后,强引用断开,Current Thread,Map Value将全部被GC回收。最好的做法就是在不需要使用ThreadLocal变量后,都调用它的remove()方法,清除数据。

所以在代码中,虽然线程池里面的任务执行完毕了,但是线程池里面的5个线程会一直存在直到JVM退出,我们set了线程的localVariable变量后没有调用localVariable.remove()方法,导致线程池里面的5个线程的threadLocals变量里面的new LocalVariable()实例没有被释放。

其实考察ThreadLocal的实现,我们可以看见,无论是get(),set()在某些时候,都会调用expungeStaleEntry()方法来清除Entry中key为null的value,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄漏。只有remove()方法中显示调用了expungeStaleEntry方法

6. ThreadLocal为什么使用弱引用而不是使用强引用?

1.key 使用强引用

引用ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal对象的实例不会被回收,导致Entry内存泄漏

2.Key使用弱引用

引用ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal实例也会被回收。Value在下一次ThreadLocalMap调用set,get,remove()方法的时候都有机会被回收

比较两种情况,我们发现,由于ThreadLocalMap的生命周期和Thread的一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是弱引用可以多一层保障。

因此,ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏。

当使用线程池+ThreadLocal时要小心,因为这种情况下,线程是不断的重复运行的,从而也就造就了value可能造成积累的情况。

7.错误使用ThreadLocal导致线程不安全

package com.wxc.thread.threadlocal;

import com.wxc.thread.tools.SleepTools;

public class ThreadLocalUnSafe implements Runnable{

    public static Number number = new Number(0);

    public void run() {
        //每个线程计数加一
        number.setNum(number.getNum()+1);
        //将其存储到ThreadLocal中
        value.set(number);
        SleepTools.ms(2);
        //输出num值
        System.out.println(Thread.currentThread().getName()+"="+value.get().getNum());
    }

    public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
    };

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new ThreadLocalUnSafe()).start();
        }
    }

    private static class Number {
        public Number(int num) {
            this.num = num;
        }

        private int num;

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public String toString() {
            return "Number [num=" + num + "]";
        }
    }
}

因为Number是static,不是线程私有的,是每个线程所共享的。ThreadLocalMap保存的是一个对象的引用,每次线程修改的时候都是修改同一个引用对应的对象的值,所有线程的值值最终都会变成最后一个线程所修改的值。

标签:Thread,ThreadLocalMap,ThreadLocal,value,threadLocal,线程,public
From: https://www.cnblogs.com/wxcissg/p/16823630.html

相关文章

  • ThreadLocal
    Java当中的四种引用强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)、虚引用(PhantomReference)4种,这4种引用的强度依次减弱。一,强引用Java中默......
  • ThreadLocal
    ThreadLocal初衷是在线程并发时解决变量共享问题,但由于过度设计,比如弱引用和哈希碰撞,导致理解难度大、使用成本高,反而成为了故障高发点。容易出现内存泄漏、脏数据、共享......
  • ThreadLocal原理及使用场景
    ​ThreadLocal意为线程本地变量,用于解决多线程并发时访问共享变量的问题。​所谓的共享变量指的是在堆中的实例、静态属性和数组;对于共享数据的访问受Java的内存模型(JMM......
  • threadLocal
    https://juejin.cn/post/7126708538440679460每个线程持有一个threadLocalMapkey是TheadLocal,value是泛型对象publicvoidset(Tvalue){Threadt=Threa......
  • ThreadLocal、InheritThreadLocal、TransmittableThreadLocal
    一、ThreadLocal多线程是Java实现多任务的基础,​​Thread​​​对象代表一个线程,我们可以在代码中调用​​Thread.currentThread()​​获取当前线程。例如,打印日志时,可以同......
  • ThreadLocal本地局部线程demo
    ThreadLocal本地局部线程demoimportorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.util.HashMap;importjava.util.Map;/***本工具只能保存一......
  • 面试必备:ThreadLocal详解
    前言大家好,我是捡田螺的小男孩。无论是工作还是面试,我们都会跟ThreadLocal打交道,今天就跟大家聊聊ThreadLocal哈~ThreadLocal是什么?为什么要使用ThreadLocal一个Thre......
  • 惊!ThreadLocal你怎么动不动就内存泄漏?
    “今天无聊带大家分析下ThreadLocal为什么会内存泄漏~前言使用ThreadLocal不当可能会导致内存泄露,是什么原因导致的内存泄漏呢?正文我们首先看一个例子,代码如下:pub......
  • ThreadLocal夺命11连问
    前言前一段时间,有同事使用ThreadLocal踩坑了,正好引起了我的兴趣。所以近期,我抽空把ThreadLocal的源码再研究了一下,越看越有意思,发现里面的东西还真不少。我把精华浓缩了......
  • ThreadLocal
    ThreadLocal是一个数据结构,有点像HashMap,可以保存key-value键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。ThreadLocal为变量在每个线程中都创建一个......