`
刘小小尘
  • 浏览: 67577 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

ThreadLocal类简单介绍

 
阅读更多

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。 ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。

线程局部变量并不是Java的新发明,很多语言(如IBM XL、FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供语言级支持,而以一种变通的方法,通过ThreadLocal的类提供支持。所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,这也是为什么线程局部变量没有在Java开发者中得到很好普及的原因。

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

  • void set(Object value) 设置当前线程的线程局部变量的值;
  • public Object get() 该方法返回当前线程所对应的线程局部变量;
  • public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度;
  • 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类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:

    代码清单9-3 SimpleThreadLocal

    public class SimpleThreadLocal {
    	private Map valueMap = Collections.synchronizedMap(new HashMap());
    	public void set(Object newValue) {
                    //①键为线程对象,值为本线程的变量副本
    		valueMap.put(Thread.currentThread(), newValue);
    	}
    	public Object get() {
    		Thread currentThread = Thread.currentThread();
    
                    //②返回本线程对应的变量
    		Object o = valueMap.get(currentThread); 
                    
                    //③如果在Map中不存在,放到Map中保存起来
                   if (o == null && !valueMap.containsKey(currentThread)) {
    			o = initialValue();
    			valueMap.put(currentThread, o);
    		}
    		return o;
    	}
    	public void remove() {
    		valueMap.remove(Thread.currentThread());
    	}
    	public Object initialValue() {
    		return null;
    	}
    }


    一个ThreadLocal实例

    package com.baobaotao.basic;
    
    public class SequenceNumber {
         
            //①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
    	private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
    		public Integer initialValue(){
    			return 0;
    		}
    	};
         
            //②获取下一个序列值
    	public int getNextNum(){
    		seqNum.set(seqNum.get()+1);
    		return seqNum.get();
    	}
    	
    	public static void main(String[ ] args) 
    	{
              SequenceNumber sn = new SequenceNumber();
             
             //③ 3个线程共享sn,各自产生序列号
             TestClient t1 = new TestClient(sn);  
             TestClient t2 = new TestClient(sn);
             TestClient t3 = new TestClient(sn);
             t1.start();
             t2.start();
             t3.start();
    	}	
    	private static class TestClient extends Thread
    	{
    		private SequenceNumber sn;
    		public TestClient(SequenceNumber sn) {
    			this.sn = sn;
    		}
    		public void run()
    		{
                            //④每个线程打出3个序列值
    			for (int i = 0; i < 3; i++) {
    			System.out.println("thread["+Thread.currentThread().getName()+
    "] sn["+sn.getNextNum()+"]");
    			}
    		}
    	}
    }


    通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:

    thread[Thread-2] sn[1] 
    thread[Thread-0] sn[1] 
    thread[Thread-1] sn[1] 
    thread[Thread-2] sn[2] 
    thread[Thread-0] sn[2] 
    thread[Thread-1] sn[2] 
    thread[Thread-2] sn[3] 
    thread[Thread-0] sn[3] 
    thread[Thread-1] sn[3]
    


    考查输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个Sequence Number实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本

    与Thread同步机制的比较

    ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

    在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序缜密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

    而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal为每一个线程提供一个独立的变量副本,从而隔离了多个线程对访问数据的冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的对象封装,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

    由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度上简化ThreadLocal的使用,代码清单9-2就使用了JDK 5.0新的ThreadLocal<T>版本。

    概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

    分享到:
    评论

    相关推荐

      Android ThreadLocal实现原理

      `Values` 是 ThreadLocal 的内部类,它实际上实现了一个简易的 HashMap 功能,以键值对的形式存储数据。 - **set() 方法**:当调用 `set(T value)` 时,会获取当前线程,然后通过 `values(currentThread)` 获取或...

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

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

      苍穹外卖-共享当前登录用户ID.docx

      ### 苍穹外卖系统中的ThreadLocal应用:共享当前登录用户ID #### 一、背景介绍 在现代Web应用开发中,特别是在微服务架构下,如何有效地...`ThreadLocal`作为一种简单而有效的工具,在实际项目中有着广泛的应用前景。

      Springboot+mybatis多数据源整合 抽象基础类

      以上就是Spring Boot与MyBatis多数据源整合的简单介绍,实际项目中可能还需要考虑事务管理、数据源自动切换等问题。通过合理的抽象和设计,我们可以构建出高效、灵活的多数据源解决方案。在提供的压缩包文件`...

      java.lang包介绍

      在Java 2(J2SE)中,`java.lang`包添加了一些新的类和方法,如`InheritableThreadLocal`,`ThreadLocal`的扩展,使得子线程可以继承父线程的变量,以及`SecurityManager`,用于实现安全管理策略。 过时方法在Java ...

      Servlet线程安全的解决方法

      通过实现`SingleThreadModel`接口、使用`synchronized`关键字以及利用`ThreadLocal`类等方法,可以在不同程度上解决这一问题。然而,在选择具体的实现方式时,还需要根据具体的应用场景和性能需求来权衡利弊。随着...

      JAVA 多线程编程

      这种方式简单直接,但每个线程类只能实现一个Runnable接口,限制了代码的复用性。 2. 实现Runnable接口:创建一个实现了Runnable接口的类,实现run()方法。然后将Runnable对象作为参数传递给Thread类的构造器,创建...

      北大计算机系JAVA培训讲义.rar

      6. **线程与并发**:Java提供强大的并发支持,讲义会讲解线程的创建、同步和通信,包括synchronized关键字、wait()、notify()方法和ThreadLocal类的应用。 7. **网络编程**:Java的Socket编程是进行网络通信的基础...

      Core Java Vol 1 & Vol 2

      3. **多线程**:涵盖线程的创建、同步、通信,以及ThreadLocal和并发工具类的使用。 4. **网络编程**:介绍套接字(Socket)编程,以及服务器端和客户端的实现。 5. **反射机制**:讲解如何在运行时动态获取类的...

      java面试常见问题总结

      - **内部实现**:ThreadLocal类内部维护了一个HashMap类型的成员变量,用于存储每个线程的变量副本。键为线程对象,值为对应线程的变量副本。 - **应用场景**:ThreadLocal广泛应用于各种多线程编程场景中,如: - ...

      Java并发程序设计教程

      响应线程中断应正确处理InterruptedException异常,不应简单地忽略或重新抛出。创建线程时要注意提供线程名,否则在多线程环境中排查问题时会非常困难。 在多线程编程中,还应当避免创建不必要的线程,因为线程的...

      J2EE开发全程实录

      - **线程变量管理器**:通过ThreadLocal类来实现线程变量的管理,以解决多线程环境下的数据共享问题。 - **事务管理**:深入讨论了事务的基本概念、为何需要事务、事务的隔离级别等内容,并重点介绍了Spring框架中...

      Java并发编程实践-电子书-05章.pdf

      ThreadLocal&lt;Integer&gt; threadLocal = new ThreadLocal(); threadLocal.set(0); // 每个线程都可以设置自己的值 int value = threadLocal.get(); // 获取当前线程的值 ``` #### 5.2 使用阻塞队列的生产者-消费者...

      Thinking in Java

      6. **网络编程**:Java在网络编程方面有强大的支持,书中介绍了Socket编程,包括TCP和UDP协议的使用,以及ServerSocket和Socket类的API。 7. **反射与注解**:反射是Java动态性的一个重要体现,书中详细讲解了如何...

      Java后端技术面试汇总-副本.pdf

      涉及到的自定义数据结构,如简单的HashMap的实现,以及ThreadLocal原理的分析也是这一部分的重点。 3、进程和线程 在这一部分中,详细讨论了进程和线程的概念,包括它们之间的区别以及并行和并发的区别。探讨了多种...

      三歪教你学多线程1

      此外,还会介绍线程常用的工具类,如CountDownLatch、CyclicBarrier、Semaphore,以及Atomic原子类和ThreadLocal的作用与原理。这些内容涵盖了Java多线程编程的核心知识,对于理解和应用多线程编程至关重要。

      Java 就业培训教程及源代码

      3. **异常处理**:介绍Java中的异常处理机制,学习如何使用try-catch-finally语句块进行错误捕获和处理,理解不同类型的异常类,并学习如何创建自定义异常。 4. **集合框架**:详述ArrayList、LinkedList、HashSet...

    Global site tag (gtag.js) - Google Analytics