本地线程-ThreadLocal

线程本地存储是一个自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。简单来说,就是对于某个变量,针对不同的线程存储不同的值。

实例:

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @Description
 * @Author KToTo
 * @Date 2019/3/18 22:22
 **/
public class ThreadLoaclVariableHolder {
    //创建一个全局的ThreadLocal对象
    private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
        private Random random = new Random(47);
        //初始化方法,此处的Random相当于共享变量,为了使演示效果明显,
        //故将该初始化方法同步
        protected synchronized Integer initialValue() {
            return random.nextInt(1000);
        }
    };

    //提供公有的递增和获取方法
    public static void increment() {
        value.set(value.get() + 1);
    }

    public static int get() {
        return value.get();
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            pool.execute(new Accessor(i));
        }
        //与Thread.sleep(long mils)方法功能一致
        TimeUnit.SECONDS.sleep(3);
        //立即关闭线程池,不熟悉的读者可以自行百度shutdown和shutdownNow的区别
        pool.shutdownNow();
    }
}

class Accessor implements Runnable {
    private final int id;

    public Accessor(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        //响应中断
        while (!Thread.currentThread().isInterrupted()) {
            ThreadLoaclVariableHolder.increment();
            System.out.println(this);
            //对于多核处理器,此方法可以省略
            Thread.yield();
        }
    }

    @Override
    public String toString() {
        return "Accessor{" +
                "id=" + id + ":" + ThreadLoaclVariableHolder.get() +
                ‘}‘;
    }
}

原理分析

  • 从概念上来看,你可以将ThreadLocal<T>视为包含了Map<Thread, T>对象,其中保存了特定于该线程的值,但是实际上并非如此,这些特定于线程的值其实是保存在Thread对象中的,当线程终止后,这些值会作为垃圾回收。
  • 首先简单概括一下,当我们使用ThreadLocal的时候,我们想要的效果是针对该共享变量,每个线程中访问该变量的值是不一样的,而且是互不影响的。而ThreadLocal可以达到这一效果,其实是在每一个Thread对象中都有一个ThreadLocalMap的实例对象,当当前线程对改ThreadLocal进行初始化的时候,会以ThreadLocal的对象为key,值为value,存放到当前线程对象中的ThreadLocalMap。
  • 在上述的示例中,我们可以看到我们直接使用了ThreadLocal的Get方法,而不是先Set然后Get,下面让我们从Get方法入手,来了解一下ThreadLocal的实现

  

public T get() {
    Thread t = Thread.currentThread();
    //获取当前线程Thread对象中存放的ThreadLocalMap。
    ThreadLocalMap map = getMap(t);
    //判断当前对象是否存在本地线程对象
    if (map != null) {
        //判断当前线程对象中的threadLocals是否存储当前对象(注意这个this)的值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //如果上述两个判断均不满足,则进行初始化
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
//关于ThreadLocalMap类的定义请参考源码
public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
/**
 *调用重写的initialValue()方法获取初始值,
 *然后将当前ThreadLocal的初始化值添加到Thread对象的threadLocals变量中。
 **/
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;
}

private void set(ThreadLocal<?> key, Object value) {

    // We don‘t use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    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)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

ThreadLocal的副作用

  • 脏数据,线程复用会导致产生脏数据。由于线程池会重用Thread对象,那么与Thread绑定的静态属性ThreadLocal变量也会被复用
  • 内存泄露,当ThreadLocal为静态属性时,就不可以寄希望与ThreadLocal对象失去引用时,触发软引用机制来回收就不现实了。
  • 针对上述两种问题的解决办法就是在每次使用完ThreadLocal之后,必须要及时的调用remove()方法清理。

参考:《Thinking In Java》、《Java并发编程实战》、《码出高效》

原文地址:https://www.cnblogs.com/TryTired/p/10933014.html

时间: 2024-11-05 19:33:16

本地线程-ThreadLocal的相关文章

线程本地变量ThreadLocal

一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味着,如果你在一个线程里改变一个属性,全部的线程都会受到这个改变的影响. 有时,你希望程序里的各个线程的属性不会被共享. Java 并发 API提供了一个很清楚的机制叫本地线程变量即ThreadLocal. 模拟ThreadLocal类实现:线程范围内的共享变量,每个线程只能访问他自己的,不能访问别的

java线程 在其他对象上同步、线程本地存储ThreadLocal:thinking in java4 21.3.6

package org.rui.thread.concurrency; /** * 在其他对象上同步 * synchronized 块必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象 * :synchronized(this), 在 这种方式中,如果获得了synchronized块上的锁, * 那么该对象其他的synchronized方法和临界区就不能被调用了. * 因此,如果在this上同步,临界区的效果就会直接缩小在同步的范围内. * * 有时必须在另一个

本地线程变量ThreadLocal (耗时工具)

本地线程变量类 package king; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; /** * TLTimeContainer为ThreadLocalTimeContainer(本地线程变量时间容器)的缩写 * 说明:用来在任意一个方法内部置入recordTime(),以作片断时间点的

Hibernate中Session与本地线程绑定

------------------siwuxie095 Hibernate 中 Session 与本地线程绑定 1.Session 类似于 JDBC 的连接 Connection 2.Session 对象是单线程对象,只能自己使用,不能共用 将 Session 与本地线程绑定,保证 Session 对象绝对是一个单线程对象 3.Hibernate 帮助我们实现了 Session 与本地线程绑定(底层是 ThreadLocal) 4.获取与本地线程绑定的 Session (1)在 Hiberna

Java并发学习之九——使用本地线程变量

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.如果创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味着,如果你在一个线程里改变一个属性,全部的线程都会受到这个改变的影响.如果希望程序里的哥哥线程的属性不会被共享,Java并发API提供了一个很清楚的机制叫本地线程变量. 2.Java并发API包括Inheritable ThreadLocal类提供线程创建线程的值的遗传性.如果线程A有一个本地线程变量,然

线程 -- ThreadLocal

1,ThreadLocal 不是“本地线程”的意思,而是Thread 的局部变量.每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本 2,提供的接口方法 • void set(Object value)设置当前线程的线程局部变量的值. • public Object get()该方法返回当前线程所对应的线程局部变量. • public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.

[原创]java WEB学习笔记94:Hibernate学习之路---session 的管理,Session 对象的生命周期与本地线程绑定

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

Python 本地线程

1. 本地线程,保证即使是多个线程,自己的值也是互相隔离. 2.普通对象演示 import threading import time class A(): pass a=A() def func(num): a.name=num time.sleep(1) print(a.name,threading.current_thread().name) 结果 D:\virtualenv\envs\vuedjango\Scripts\python.exe D:/test/flaskTest/flask

线程本地变量ThreadLocal源码解读

  一.ThreadLocal基础知识   原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板类并未采用线程同步机制,因为线程同步会影响并发性和系统性能,而且实现难度也不小. ThreadLocal在Spring中发挥着重要的作用.在管理request作用域的bean,事务管理,任务调度,AOP等模块中都出现了它的身影. ThreadLocal介绍: 它不是一个线程,而是线程的一个本地化