关于ThreadLocal

ThreadLocal提供了与线程绑定的变量,访问变量的每个线程都具有一个该变量的副本,它们都可以对这个变量的副本进行一些独立的操作(变量的这些副本是线程隔离的,所以不存在线程安全的问题),ThreadLocal的实例经常是作为一个私有静态字段出现。在数据库连接复用的场景下(提供一个获取和关闭连接的方法),多个线程在对数据库操作时可以能会出现线程安全的问题,比如:

  • 多线程获取数据库连接时,可能会创建很多连接;
  • 一个线程在持有连接对数据库进行操作时,另一个线程却在调用连接的关闭方法;

如果使用同步锁去解决上面的问题,确实可以解决暴露的问题,但是在多线程场景下,同步锁的涉入必定会导致性能的急速下降,所以实际应用中很少会使用这种方式;还有就是各个线程需要使用数据库连接了,就直接就地new一个Connection出来,用完再关闭,这样也确实是一种方法,但是类似于数据库连接的创建和销毁都是非常耗费系统资源的,所以并不能满足连接复用的目的;这时使用ThreadLocal在每个线程中对该数据库连接对象会创建一个副本,即每个线程内部都会有一个Connection,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能,但是由于它再每个线程中都创建了某个变量的副本,所以也需要考量它对系统内存资源的耗费。

ThreadLocal提供了一些方法,get()可以获取当前线程中保存的副本,其相关源码如下(做了部分作结):

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 获取ThreadLocalMap的Key和Value,参数是this,不是t
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            // 获取成功返回ThreadLocalMap的Value
            return result;
        }
    }
    // 如果为获取到ThreadLocalMap,则调用额外方法
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

private T setInitialValue() {
    // 初始化一个null给value
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 如果为空塞个null进去
    if (map != null)
        map.set(this, value);
    else
        // 再创建一个新的ThreadLocalMap
        createMap(t, value);
    return value;
}

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

getMap(Thread t)方法最终是返回了一个ThreadLocalMap对象:

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

发现ThreadLocalMap对象的Key是ThreadLocal对象,每个Thread中都有一个ThreadLocal.ThreadLocalMap threadLocals,这个threadLocals就是用来存储实际变量副本的,key为当前的ThreadLocal,value就是变量的副本。在使用get()时,如果没有变量的副本,那就初始化一个为nullThreadLocals(此时直接调用会发生空指针异常),否则在主动调用set()时初始化。ThreadLocal在哪那么它就只对当前所在线程中的变量副本生效,不会对这个线程之外的线程产生作用。下面是网上的一个不错案例:

public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();


    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }

    public long getLong() {
        return longLocal.get();
    }

    public String getString() {
        return stringLocal.get();
    }

    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();

        // 先将当前线程的ID和Name设到对应的threadlocals中
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());


        Thread thread1 = new Thread() {
            public void run() {
                // 将新创建的线程的属性设进去,再次查看
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            }

        };
        thread1.start();

        // 必须等thread1执行完
        thread1.join();

        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

输出为:

1
main
12
Thread-0
1
main

第一次输出是在Main线程中,设置了两个ThreadLocal,输出就只是在Main线程中设进去的变量,第二次是在thread1线程中,重新设置了两个ThreadLocal,输出是变成了新设的值,等最后thread1线程执行完后,再次输出,因为此时是在Main线程中,所以输出的是在Main线程最开始设置的那两个值,Main线程和thread1线程之间是不影响的,thread1中的设置不会更改了在Main线程中设置的值。Main线程和thread1线程都有各自的threadlocals变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是Java中一个非常重要的线程封闭技术。它可以让每个线程都拥有自己的变量副本,避免了线程间的竞争和数据泄露问题。在本文中,我们将详细介绍ThreadLocal的定义、用法及其优点。 1. ThreadLocal的定义 ThreadLocal是Java中一个用来实现线程封闭技术的类。它提供了一个本地线程变量,可以在多线程环境下使每个线程都拥有自己的变量副本。每个线程都可以独立地改变自己的副本,而不会影响到其他线程的副本。ThreadLocal的实现是基于ThreadLocalMap的,每个ThreadLocal对象都对应一个ThreadLocalMap,其中存储了线程本地变量的值。 2. ThreadLocal的用法 使用ThreadLocal非常简单,只需要创建一个ThreadLocal对象,然后调用其get()和set()方法即可。get()方法用来获取当前线程的变量副本,如果当前线程还没有变量副本,则会创建一个新的副本并返回。set()方法用来设置当前线程的变量副本,如果当前线程已经有了变量副本,则会覆盖原来的副本。 下面是一个简单的例子,演示了如何使用ThreadLocal来实现线程封闭: ```java public class ThreadLocalTest { private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) throws InterruptedException { new Thread(() -> { threadLocal.set("Thread A"); System.out.println("Thread A: " + threadLocal.get()); }).start(); new Thread(() -> { threadLocal.set("Thread B"); System.out.println("Thread B: " + threadLocal.get()); }).start(); Thread.sleep(1000); System.out.println("Main: " + threadLocal.get()); } } ``` 运行结果如下: ``` Thread A: Thread A Thread B: Thread B Main: null ``` 从输出结果可以看出,每个线程都拥有自己的变量副本,互不影响。而在主线程中,由于没有设置过变量副本,所以返回null。 3. ThreadLocal的优点 ThreadLocal的优点主要体现在以下几个方面: (1)线程安全:ThreadLocal可以避免线程间的竞争和数据泄露问题,每个线程都可以独立地修改自己的变量副本,不会影响其他线程。 (2)高效性:ThreadLocal使用起来非常简单,而且性能也非常高,比如在Web开发中,可以将用户信息存储在ThreadLocal中,从而避免在每个方法中都去查询数据库。 (3)易用性:ThreadLocal的使用非常灵活,可以根据实际需要自由地定义数据类型和访问方式。 总的来说,ThreadLocal是Java中一个非常重要的线程封闭技术,可以帮助开发人员避免线程间的竞争和数据泄露问题,提高程序的安全性和性能。在实际开发中,我们应该充分利用ThreadLocal的优点,合理地运用它来解决各种线程安全问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值