`
wxyfighting
  • 浏览: 200842 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

ThreadLocal实现方式&使用介绍---无锁化线程封闭

 
阅读更多

虽然现在可以说很多程序员会用ThreadLocal,但是我相信大多数程序员还不知道ThreadLocal,而使用ThreadLocal的程序员大多只是知道其然而不知其所以然,因此,使用ThreadLocal的程序员很多时候会被它导入到陷进中去,其实java很多高级机制系列的很多东西都是一把双刃剑,也就是有利必有其弊,那么我们的方法是找到利和弊的中间平衡点,最佳的方式去解决问题。

本文首先说明ThreadLocal能做什么,然后根据功能为什么要用它,如何使用它,最后通过内部说明讲解他的坑在哪里,使用的人应该如何避免坑。

ThreadLocal的定义和用途的概述(我的理解):

它是一个线程级别变量,在并发模式下是绝对安全的变量,也是线程封闭的一种标准用法(除了局部变量外),即使你将它定义为static,它也是线程安全的。

ThreadLocal能做什么呢?

这个一句话不好说,我们不如来看看实际项目中遇到的一些困解:当你在项目中根据一些参数调用进入一些方法,然后方法再调用方法,进而跨对象调用方法,很多层次,这些方法可能都会用到一些相似的参数,例如,A中需要参数a、b、c,A调用B后,B中需要b、c参数,而B调用C方法需要a、b参数,此时不得不将所有的参数全部传递给B,以此类推,若有很多方法的调用,此时的参数就会越来越繁杂,另外,当程序需要增加参数的时候,此时需要对相关的方法逐个增加参数,是的,很麻烦,相信你也遇到过,这也是在C语言面向对象过来的一些常见处理手段,不过我们简单的处理方法是将它包装成对象传递进去,通过增加对象的属性就可以解决这个问题,不过对象通常是有意义的,所以有些时候简单的对象包装增加一些扩展不相关的属性会使得我们class的定义变得十分的奇怪,所以在这些情况下我们在架构这类复杂的程序的时候,我们通过使用一些类似于Scope的作用域的类来处理,名称和使用起来都会比较通用,类似web应用中会有context、session、request、page等级别的scope,而ThreadLocal也可以解决这类问题,只是他并不是很适合解决这类问题,它面对这些问题通常是初期并没有按照scope以及对象的方式传递,认为不会增加参数,当增加参数时,发现要改很多地方的地方,为了不破坏代码的结构,也有可能参数已经太多,已经使得方法的代码可读性降低,增加ThreadLocal来处理,例如,一个方法调用另一个方法时传入了8个参数,通过逐层调用到第N个方法,传入了其中一个参数,此时最后一个方法需要增加一个参数,第一个方法变成9个参数是自然的,但是这个时候,相关的方法都会受到牵连,使得代码变得臃肿不堪。

上面提及到了ThreadLocal一种亡羊补牢的用途,不过也不是特别推荐使用的方式,它还有一些类似的方式用来使用,就是在框架级别有很多动态调用,调用过程中需要满足一些协议,虽然协议我们会尽量的通用,而很多扩展的参数在定义协议时是不容易考虑完全的以及版本也是随时在升级的,但是在框架扩展时也需要满足接口的通用性和向下兼容,而一些扩展的内容我们就需要ThreadLocal来做方便简单的支持。

简单来说,ThreadLocal是将一些复杂的系统扩展变成了简单定义,使得相关参数牵连的部分变得非常容易,以下是我们例子说明:

Spring的事务管理器中,对数据源获取的Connection放入了ThreadLocal中,程序执行完后由ThreadLocal中获取connection然后做commit和rollback,使用中,要保证程序通过DataSource获取的connection就是从spring中获取的,为什么要做这样的操作呢,因为业务代码完全由应用程序来决定,而框架不能要求业务代码如何去编写,否则就失去了框架不让业务代码去管理connection的好处了,此时业务代码被切入后,spring不会向业务代码区传入一个connection,它必须保存在一个地方,当底层通过ibatis、spring jdbc等框架获取同一个datasource的connection的时候,就会调用按照spring约定的规则去获取,由于执行过程都是在同一个线程中处理,从而获取到相同的connection,以保证commit、rollback以及业务操作过程中,使用的connection是同一个,因为只有同一个conneciton才能保证事务,否则数据库本身也是不支持的。

其实在很多并发编程的应用中,ThreadLocal起着很重要的重要,它不加锁,非常轻松的将线程封闭做得天衣无缝,又不会像局部变量那样每次需要从新分配空间,很多空间由于是线程安全,所以,可以反复利用线程私有的缓冲区。

如何使用ThreadLocal?

在系统中任意一个适合的位置定义个ThreadLocal变量,可以定义为public static类型(直接new出来一个ThreadLocal对象),要向里面放入数据就使用set(Object),要获取数据就用get()操作,删除元素就用remove(),其余的方法是非public的方法,不推荐使用。

下面是一个简单例子(代码片段1):

public class ThreadLocalTest2 {
	
	public final static ThreadLocal <String>TEST_THREAD_NAME_LOCAL = new ThreadLocal<String>();

	public final static ThreadLocal <String>TEST_THREAD_VALUE_LOCAL = new ThreadLocal<String>();
	
	public static void main(String[]args) {
		for(int i = 0 ; i < 100 ; i++) {
			final String name = "线程-【" + i + "】";
			final String value =  String.valueOf(i);
			new Thread() {
				public void run() {
					try {
						TEST_THREAD_NAME_LOCAL.set(name);
						TEST_THREAD_VALUE_LOCAL.set(value);
						callA();
					}finally {
						TEST_THREAD_NAME_LOCAL.remove();
						TEST_THREAD_VALUE_LOCAL.remove();
					}
				}
			}.start();
		}
	}
	
	public static void callA() {
		callB();
	}
	
	public static void callB() {
		new ThreadLocalTest2().callC();
	}
	
	public void callC() {
		callD();
	}
	
	public void callD() {
		System.out.println(TEST_THREAD_NAME_LOCAL.get() + "\t=\t" + TEST_THREAD_VALUE_LOCAL.get());
	}
}

这里模拟了100个线程去访问分别设置namevalue,中间故意将namevalue的值设置成一样,看是否会存在并发的问题,通过输出可以看出,线程输出并不是按照顺序输出,说明是并行执行的,而线程namevalue是可以对应起来的,中间通过多个方法的调用,以模实际的调用中参数不传递,如何获取到对应的变量的过程,不过实际的系统中往往会跨类,这里仅仅在一个类中模拟,其实跨类也是一样的结果,大家可以自己去模拟就可以。

相信看到这里,很多程序员都对ThreadLocal的原理深有兴趣,看看它是如何做到的,尽然参数不传递,又可以像局部变量一样使用它,的确是蛮神奇的,其实看看就知道是一种设置方式,看到名称应该是是和Thread相关,那么废话少说,来看看它的源码吧,既然我们用得最多的是set、get和remove,那么就从set下手:

set(T obj)方法为(代码片段2):

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

首先获取了当前的线程,和猜测一样,然后有个getMap方法,传入了当前线程,我们先可以理解这个map是和线程相关的map,接下来如果 不为空,就做set操作,你跟踪进去会发现,这个和HashMap的put操作类似,也就是向map中写入了一条数据,如果为空,则调用createMap方法,进去后,看看(代码片段3):

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

返现创建了一个ThreadLocalMap,并且将传入的参数和当前ThreadLocal作为K-V结构写入进去(代码片段4):

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

这里就不说明ThreadLocalMap的结构细节,只需要知道它的实现和HashMap类似,只是很多方法没有,也没有implements Map,因为它并不想让你通过某些方式(例如反射)获取到一个Map对他进一步操作,它是一个ThreadLocal里面的一个static内部类,default类型,仅仅在java.lang下面的类可以引用到它,所以你可以想到Thread可以引用到它。

我们再回过头来看看getMap方法,因为上面我仅仅知道获取的Map是和线程相关的,而通过代码片段3,有一个t.threadLocalMap = new ThreadLocalMap(this, firstValue)的时候,相信你应该大概有点明白,这个变量应该来自Thread里面,我们根据getMap方法进去看看:


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

是的,是来自于Thread,而这个Thread正好又是当前线程,那么进去看看定义就是:

ThreadLocal.ThreadLocalMap threadLocals = null;

这个属性就是在Thread类中,也就是每个Thread默认都有一个ThreadLocalMap,用于存放线程级别的局部变量,通常你无法为他赋值,因为这样的赋值通常是不安全的。

好像是不是有点乱,不着急,我们回头先摸索下思路:

1、Thread里面有个属性是一个类似于HashMap一样的东西,只是它的名字叫ThreadLocalMap,这个属性是default类型的,因此同一个package下面所有的类都可以引用到,因为是Thread的局部变量,所以每个线程都有一个自己单独的Map,相互之间是不冲突的,所以即使将ThreadLocal定义为static线程之间也不会冲突。

2、ThreadLocal和Thread是在同一个package下面,可以引用到这个类,可以对他做操作,此时ThreadLocal每定义一个,用this作为Key,你传入的值作为value,而this就是你定义的ThreadLocal,所以不同的ThreadLocal变量,都使用set,相互之间的数据不会冲突,因为他们的Key是不同的,当然同一个ThreadLocal做两次set操作后,会以最后一次为准。

3、综上所述,在线程之间并行,ThreadLocal可以像局部变量一样使用,且线程安全,且不同的ThreadLocal变量之间的数据毫无冲突。

我们继续看看get方法和remove方法,其实就简单了:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }	

通过根据当前线程调用getMap方法,也就是调用了t.threadLocalMap,然后在map中查找,注意Map中找到的是Entry,也就是K-V基本结构,因为你set写入的仅仅有值,所以,它会设置一个e.value来返回你写入的值,因为Key就是ThreadLocal本身。你可以看到map.getEntry也是通过this来获取的。

同样remove方法为:

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

同样根据当前线程获取map,如果不为空,则remove,通过this来remove。







分享到:
评论

相关推荐

    Java中的线程同步与ThreadLocal无锁化线程封闭实现

    ThreadLocal,全称为`java.lang.ThreadLocal`,是Java提供的一种线程局部变量,它的每个线程都有自己的副本,线程间互不影响,从而实现无锁化线程封闭。ThreadLocal不是用来解决线程同步问题的,而是用来避免线程间...

    ThreadLocal 线程本地变量 及 源码分析.rar_开发_设计

    以上是对ThreadLocal的简单介绍和源码分析,实际使用中还需要结合具体业务场景进行合理设计和优化。通过深入理解ThreadLocal的工作原理,可以帮助我们更好地利用这一工具,提高代码的并发性能和可维护性。

    线程ThreadLocal机制实现例子

    本例以序列号生成的程序为例,展示ThreadLocal的使用

    ThreadLocal应用示例及理解

    假设我们需要一个线程安全的计数器,可以使用ThreadLocal实现: ```java public class ThreadLocalCounter { private static ThreadLocal&lt;Integer&gt; counter = new ThreadLocal(); public static void increment...

    java核心知识点学习----多线程间的数据共享和对象独立,ThreadLocal详解.pdf

    在Java中,有多种方式可以实现线程间的数据共享和对象独立,其中ThreadLocal是常用的工具之一。 在多线程环境下,共享数据通常会引发线程安全问题,比如上述例子中的“张三给李四转钱”场景,如果两个线程同时操作...

    ThreadLocal原理及在多层架构中的应用

    - **简单易用**:ThreadLocal提供了一种简单的线程间数据隔离的实现方式,代码简洁,无需复杂的同步机制。 - **性能提升**:避免了线程同步的开销,提高了程序执行效率。 缺点: - **内存泄漏风险**:如果不正确地...

    java ThreadLocal多线程专属的变量源码

    java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多...

    java线程封闭之栈封闭和ThreadLocal

    栈封闭和 ThreadLocal 是 Java 线程封闭中两种常见的实现方式。 栈封闭 栈封闭是将资源封闭在每个线程的栈中,以保证线程安全。由于每个线程都有自己的虚拟机栈,所以在栈中存放的资源是线程私有的,不会被其他...

    java中ThreadLocal类的使用

    总结,`ThreadLocal`是Java中用于实现线程局部变量的工具,它提供了一种简单的方式在多线程环境下隔离数据,避免了传统的同步机制。但是,使用时需注意其潜在的内存泄漏风险和对代码可读性的影响,合理地选择使用...

    ThreadLocal

    ThreadLocal通常被用来解决线程共享数据时可能出现的并发问题,避免了使用synchronized关键字进行同步控制的复杂性。 在Java中,ThreadLocal的工作原理是为每个线程创建一个单独的存储空间,每个线程可以独立地读写...

    简单分析Java线程编程中ThreadLocal类的使用共

    总结来说,Java线程编程中的ThreadLocal类提供了一种高效、便捷的方式来实现线程间的隔离数据,但同时也需要开发者注意其潜在的内存泄漏风险。正确理解和使用ThreadLocal,能够帮助我们编写出更加健壮、高效的并发...

    java事务 - threadlocal

    当Java事务与ThreadLocal结合使用时,可以在不同的线程中维护各自的事务状态,比如在Spring框架中,每个线程的ThreadLocal可以存储一个TransactionStatus对象,这样就可以在线程内部管理当前事务的状态,而不会影响...

    从面试中的问题分析ThreadLocal

    * 在多线程环境下,使用ThreadLocal可以实现线程之间的变量隔离,避免变量的污染。 * 在数据库连接池中,使用ThreadLocal可以实现每个线程的连接对象的隔离。 ThreadLocal的优缺点 -------------------- ### 优点...

    Java多线程 之 临界区、ThreadLocal.docx

    在这个例子中,`ThreadLocalTest`类中的`threadLocal`变量为每个线程初始化一个随机值,并且`increament()`方法会增加当前线程的`ThreadLocal`变量的值。由于每个线程都有自己的副本,因此不会发生数据竞争。 总结...

    Spring事务处理-ThreadLocal的使用

    在Spring事务管理中,连接池与ThreadLocal结合,确保每个线程在事务内使用的是同一连接,避免了事务间的干扰。例如,`HikariCP`和`C3P0`等流行的连接池实现都支持与Spring事务管理的无缝集成。 在实际应用中,理解...

    Java中ThreadLocal的设计与使用

    理解ThreadLocal的工作原理和使用方法对于编写高效、安全的多线程程序至关重要。 ### ThreadLocal简介 ThreadLocal并非一个线程对象,而是一个线程局部变量的容器。每个线程都有自己的ThreadLocal实例,它们各自...

    静态代理案例---线程买票

    在Java中,我们可以使用以下几种方式实现线程同步: 1. **synchronized关键字**:这是Java中最基础的同步机制。synchronized可以修饰方法或代码块,确保在同一时刻只有一个线程能访问被修饰的代码。在本案例中,...

    java 简单的ThreadLocal示例

    Java中的ThreadLocal是一个非常重要的工具类,它在多线程编程中扮演着独特角色,尤其在处理线程间数据隔离和共享时。ThreadLocal不是线程本身,而是为每个线程提供一个独立的变量副本,使得每个线程都可以独立地改变...

Global site tag (gtag.js) - Google Analytics