lesson1:threadlocal的使用demo及源码分析

本文中所使用的demo源码地址:https://github.com/mantuliu/javaAdvance 其中的类Lesson1ThreadLocal

本文为java晋级系列的第一讲,后续会陆续推出java相关的高级应用和分析。我个人一直都比较推崇threadlocal的设计原理和实现方式。以下关于threadlocal的描述来源于百度百科:

ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。

所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。

ThreadLocal的接口方法

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

void set(Object value)

public void remove()

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

protected Object initialValue()

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

值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread中都有一个该类型的变量——threadLocals——用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

     threadlocal强调的存储,它也不是java独有的概念,更准确的是它应该叫做threadlocal storage,是线程的本地存储,在我们的工作中,我们一定会遇到一种场景,一个用户在服务中经过了很多次的操作,当我们使用log4j和logback打印日志时,希望在日志中能清楚的标记出同一个用户的所有操作,这时,我们的日志系统其实就是使用了threadlocal来实现的此功能,在同一个线程中存储和传递变量。

下面的代码是一个threadlocal的使用例子:

package com.mantu.advance;

/**
 * blog http://www.cnblogs.com/mantu/
 * github https://github.com/mantuliu/
 * @author mantu
 *
 */
public class Lesson1ThreadLocal {
    public static ThreadLocal<String> local = new ThreadLocal<String>();//声明静态的threadlocal变量
    public static ThreadLocal<String> local2 = new ThreadLocal<String>();//声明静态的threadlocal变量
    public static void main(String [] args){
        for(int i=0;i<5;i++){
            TestThread testThread = new TestThread();//创建5个线程
            new Thread(testThread).start();
        }
    }

}

class TestThread implements Runnable{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            Thread.sleep(1l);//让线程停顿一下,便于其它线程执行
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        Lesson1ThreadLocal.local.set(Thread.currentThread().getId()+":"+System.currentTimeMillis());
        Lesson1ThreadLocal.local2.set(Thread.currentThread().getId()+"");
        firstStep();
        try {
            Thread.sleep(1l);//让线程停顿一下,便于其它线程执行
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        secondStep();
        try {
            Thread.sleep(1l);//让线程停顿一下,便于其它线程执行
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        thirdStep();
        try {
            Thread.sleep(1l);//让线程停顿一下,便于其它线程执行
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        fourthStep();
        try {
            Thread.sleep(1l);//让线程停顿一下,便于其它线程执行
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        fStep();
    }

    public void firstStep(){
        System.out.println(Lesson1ThreadLocal.local.get().toString()+":first step");//获取本线程的threadlocal变量值并打印
    }
    public void secondStep(){
        System.out.println(Lesson1ThreadLocal.local.get().toString()+":second step");
    }
    public void thirdStep(){
        System.out.println(Lesson1ThreadLocal.local.get().toString()+":third step");
    }
    public void fourthStep(){
        System.out.println(Lesson1ThreadLocal.local.get().toString()+":fourth step");
    }
    public void fStep(){
        System.out.println(Lesson1ThreadLocal.local.get().toString()+":fifth step");
    }
}

代码的主要思路是5个线程,使用了同一个静态的threadlocal变量,每个线程在启动时,存储本线程相关的变量,在后面的5个步骤中都会使用到,展示每个线程的所使用的变量都是独立的,执行结果如下,大家可以自行执行并观察执行结果:

9:1470882533007:first step
11:1470882533023:first step
10:1470882533024:first step
13:1470882533024:first step
9:1470882533007:second step
12:1470882533024:first step
9:1470882533007:third step
12:1470882533024:second step
13:1470882533024:second step
11:1470882533023:second step
10:1470882533024:second step
11:1470882533023:third step
10:1470882533024:third step
12:1470882533024:third step
9:1470882533007:fourth step
13:1470882533024:third step
11:1470882533023:fourth step
10:1470882533024:fourth step
12:1470882533024:fourth step
13:1470882533024:fourth step
9:1470882533007:fifth step
12:1470882533024:fifth step
10:1470882533024:fifth step
13:1470882533024:fifth step
11:1470882533023:fifth step

从执行结果标注红色的部分可以看出,线程id为9的线程在step1到step5的操作过程中,从threadlocal变量中所取到变量值是同一个:9:1470882533007,由此便巧妙的利用了threadlocal变量来实现了在同一个线程内部的变量共享功能,对于同一个变量的操作与其它线程隔离。

下面我们开始分析一下threadlocal的源码,首先从set()方法看起:

    public void set(T value) {
        Thread t = Thread.currentThread();//获取到当前的线程
        ThreadLocalMap map = getMap(t);//通过当前的线程来获取到本线程对应的存储map
        if (map != null)
            map.set(this, value);//如果map不为空,则在map中存储值value,对应的key为当前的threadlocal对象
        else
            createMap(t, value);//如果map为空,则创建map,并在map中存储此变量
    }

接下来,我们再分析一下createMap()相关的代码

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);//创建ThreadLocalMap,参数为threadlocal变量和之前传递的变量值
    }

        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];//存储变量的数组
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);//将变量存储到数组里
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

我们再来看看get()的源码

    public T get() {
        Thread t = Thread.currentThread();//一样的获取当前的线程
        ThreadLocalMap map = getMap(t);//因为每个线程都有一个独立的map空间,通过线程获取到这个map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//通过key值,也就是我们的local变量来获取到实际的变量值
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

从源码中,我们可以发现,每个线程都有一个独立的存储空间,此空间是一个map,map的key值是我们所使用的threadlocal变量,例如文中的ThreadLocal<String> local 变量,此key对应的值为我们存储的变量Thread.currentThread().getId()+":"+System.currentTimeMillis()。通过下面的图可以更好的帮助大家来理解threadlocal变量中线程、存储空间map、threadlocal变量、存储的变量四者间的关系:一个thread有且只有一个存储空间(map),map会对应多个键值对,其中键为threadlocal变量,值为业务使用的实际变量。

时间: 2024-10-12 23:20:14

lesson1:threadlocal的使用demo及源码分析的相关文章

ThreadLocal 工作原理、部分源码分析

1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocalMap 静态内部类维护了一个Entry 数组 private Entry[] table; 查看Entry 源码,它维护了两个属性,ThreadLocal 对象 与一个Object static class Entry extends W

ASimpleCache(ACache)源码分析(android轻量级开源缓存框架)

转载请注明出处:http://blog.csdn.net/zhoubin1992/article/details/46379055 ASimpleCache框架源码链接 https://github.com/yangfuhai/ASimpleCache 杨神作品,大家最熟悉他的应该是afinal框架吧 官方介绍 ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架.轻量到只有一个java文件(由十几个类精简而来). 1.它可以缓存什么东西? 普通的字符串.JsonObj

并发编程(四):ThreadLocal从源码分析总结到内存泄漏

一.目录 1.ThreadLocal是什么?有什么用? 2.ThreadLocal源码简要总结? 3.ThreadLocal为什么会导致内存泄漏? 二.ThreadLocal是什么?有什么用? 引入话题:在并发条件下,如何正确获得共享数据?举例:假设有多个用户需要获取用户信息,一个线程对应一个用户.在mybatis中,session用于操作数据库,那么设置.获取操作分别是session.set().session.get(),如何保证每个线程都能正确操作达到想要的结果? /** * 回顾sync

Firefly——dbentrust 示例DEMO (源码+教程)

原地址:http://www.9miao.com/question-15-54002.html Firefly——dbentrust示例说明一.数据库准备本篇示例演示的是firefly与MySQL和memcached之间的数据处理,所以要先准备好数据库.(数据库工具使用的是SQLyogEnt)1.创建数据库 <ignore_js_op> 2.建表下面是一个简单的角色表(player) <ignore_js_op> 二.firefly与MySQL之间的交互(test_dbpool.p

Java中线程局部变量ThreadLocal使用教程及源码分析

在Java多线程编程中有时候会遇见线程本地局部变量ThreadLocal这个类,下面就来讲讲ThreadLocal的使用及源码分析. ThreadLocal 是Thread Local Varial(线程局部变量)的意思,每个线程在使用线程局部变量的时候都会为使用这个线程局部变量的线程提供一个线程局部变量的副本,使得每个线程都可以完全独立地操作这个线程局部变量,而不会与其他线程发生冲突,从线程的角度来看,每个线程都好像独立地拥有了这个线程局部变量.这样,看似每个线程都在并发访问同一个资源(线程局

ThreadLocal 源码分析

1.个人总结和想法: (1).ThreadLocal的内存泄漏问题? ThreadLocal 我们应该关注它的内存泄漏问题,原因虽然JDK开发者已经使用了弱引用的键来尝试解决这个问题,不过是依然存在很大风险的,因为当使用static的ThreadLocal时会使其生命周期和类一样,这样是没有必要的,就造成了内存泄漏,还有我们使用完了没有及时的清除ThreadLocal也会导致内存泄漏,内存泄漏就是让本应该被回收的对象还依然占用着内存. (2)ThreadLocal中的对象关系? ThreadLo

ThreadLocal介绍以及源码分析

ThreadLocal 线程主变量 前面部分引用其他优秀博客,后面源码自己分析的,如有冒犯请私聊我. 用Java语言开发的同学对 ThreadLocal 应该都不会陌生,这个类的使用场景很多,特别是在一些框架中经常用到,比如数据库事务操作,还有MVC框架中数据跨层传递.这里我们简要探讨下 ThreadLocal 的内部实现及可能存在的问题. 首先问自己一个问题,让自己实现一个这个的功能类的话怎么去做?第一反应就是简单构造一个 Map<Thread, T> 数据结构,key是 Thread,va

【JAVA】ThreadLocal源码分析

ThreadLocal内部是用一张哈希表来存储: 1 static class ThreadLocalMap { 2 static class Entry extends WeakReference<ThreadLocal<?>> { 3 /** The value associated with this ThreadLocal. */ 4 Object value; 5 6 Entry(ThreadLocal<?> k, Object v) { 7 super(k)

ThreadLocal定义、使用案例及源码分析

原文连接:(http://www.studyshare.cn/blog-front//blog/details/1165/0 ) 一.ThreadLocal定义 jdk官方文档定义是:该类提供线程局部变量. 这些变量与其正常的对应方式不同,因为访问一个线程(通过其@code get或@code set方法)的每个线 程都有自己的独立初始化变量副本. 通俗来讲就是:使用ThreadLocal包装后的对象,在ThreadLocal所在线程中会有一个对象副本,该副本只会在拥有它的线程中使用,别的线程无