ThreadLocal对象的使用

本文详细介绍了Java中的ThreadLocal对象,包括其工作原理、如何避免线程安全问题以及内存泄漏的处理。通过实例展示了ThreadLocal如何为每个线程提供独立变量副本,并强调了及时清理值的重要性以防止内存泄漏。
摘要由CSDN通过智能技术生成

ThreadLocal对象的使用

一、引言

先看两段代码,看看执行结果有什么不同

第一段

public class ThreadLocalTest2 implements Runnable{

    private static Integer a = 0;

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            ++a;
            System.out.println(Thread.currentThread().getName() + ":" + a);
        }
    }

    public static void main(String[] args) {
        Runnable runnable = new ThreadLocalTest2();
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
        t1.start();
        t2.start();
        t3.start();
    }
}

执行结果是

image-20231025194409924

第二段

public class ThreadLocalTest implements Runnable{

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            threadLocal.set(threadLocal.get() + 1);
            Integer val = threadLocal.get();
            System.out.println(Thread.currentThread().getName() + ":" + val);
        }
        threadLocal.remove();
    }

    public static void main(String[] args) {
        Runnable runnable = new ThreadLocalTest();
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
        t1.start();
        t2.start();
        t3.start();
    }
}

执行结果是

image-20231025194515303

在第一段代码中,三个线程操作的变量是同一个,所以最后结果会累加到15;

而在第二段代码中,最后结果为三个线程最后分别输出5,说明操作的变量不是同一个,那么ThreadLocal到底是什么呢?它又是如何实现类似局部变量操作的呢?

二、简介

ThreadLocal 是Java中的一个线程局部变量。当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程。

三、原理

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();
}
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
            
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
        
    private Entry[] table;
}

原理概述:

每个线程都有一个 ThreadLocalMap(ThreadLocal内部类),Map 中元素的键为 ThreadLocal对象,而值对应线程的变量副本,所以多线程对同一个ThreadLocal对象进行get和set操作的是对应线程的变量副本,不会有任何线程不安全的问题。

调用流程:

调用 threadLocal.set() ——> 调用getMap(Thread) ——> 返回当前线程的 ThreadLocalMap < ThreadLocal, value> ——> map.set(this, value),this 是 ThreadLocal
调用 threadLocal.get() ——> 调用getMap(Thread) ——> 返回当前线程的 ThreadLocalMap < ThreadLocal, value> ——> map.getEntry(this),返回value。

四、使用

  • Object get():获取该线程局部变量的值。

  • void set(Object value):给该线程局部变量赋值。

  • protected Object initialValue():返回该线程局部变量的初始值,可以重写该方法来设置初始值。

  • public void remove():将当前线程局部变量的值删除。

    简单使用如下:

    public class MyThread implements Runnable {
        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                //设置初始值为0
                return 0;
            }
        };
    
        @Override
        public void run() {
            // 设置变量值
            threadLocal.set((int) (Math.random() * 100));
            
            // 获取变量值
            int value = threadLocal.get();
            System.out.println(Thread.currentThread().getName() + " 的变量值为:" + value);
            
            // 清除变量值
            threadLocal.remove();
        }
    
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            
            // 创建多个线程
            Thread thread1 = new Thread(myThread, "线程1");
            Thread thread2 = new Thread(myThread, "线程2");
            Thread thread3 = new Thread(myThread, "线程3");
            
            // 启动线程
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }
    

​ 结果如下:

image-20231025215959777

五、内存泄漏

ThreadLocal内部维护了一个Entry数组,它的长度默认为16。每个Entry对象都包含了一个弱引用(ThreadLocal对象)和一个强引用(值对象)。当使用ThreadLocal的set方法设置值时,实际上是通过ThreadLocal对象的hashCode()方法计算出一个索引位置,然后将值存储在对应的Entry对象中。当使用ThreadLocal的get方法获取值时,也是通过ThreadLocal对象的hashCode()方法计算出索引位置,并从对应的Entry对象中取出值。

当线程结束后,如果没有手动调用ThreadLocal的remove()方法来清理ThreadLocal中的值,那么这些值将会一直存在于Entry数组中,而且由于Entry对象中的ThreadLocal对象是弱引用,所以这些ThreadLocal对象可能会被垃圾回收器回收,但对应的值对象却无法被回收。这就导致了内存泄漏的问题,即ThreadLocal中的值无法被释放,占用着内存资源。

解决方法为:

  1. 在使用ThreadLocal的地方,及时调用remove()方法来清理ThreadLocal中的值,以确保值对象能够被回收。

    //使用完成之后,进行remove()清除
    threadLocal.remove();
    
  2. 使用弱引用来存储ThreadLocal中的值,当线程结束后,ThreadLocal对象被垃圾回收器回收时,对应的值对象也会被回收。

    // 使用弱引用来存储值
    private static ThreadLocal<WeakReference<Integer>> threadLocal = new ThreadLocal<>();
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值