帮助理解Java中ThreadLocal的一篇文章

原文地址:http://www.ahlinux.com/java/20332.html

并发编程中,一个重要的内容是数据共享。当你创建了实现Runnable接口的线程,然后开启使用相同Runnable实例的各种Thread对象,所有的线程便共享定义在Runnable对象中的属性。也就是说,当你在一个线程中改变任意属性时,所有的线程都会因此受到影响,同时会看到第一个线程修改后的值。有时我们希望如此,比如:多个线程增大或减小同一个计数器变量;但是,有时我们希望确保每个线程,只能工作在它自己的线程实例的拷贝上,同时不会影 响其他线程的数据。

使用ThreadLocal的时机

举个例子,想象你在开发一个电子商务应用,你需要为每一个控制器处理的顾客请求,生成一个唯一的事务ID,同时将其传到管理器或DAO的业务方法中,以便记录日志。一种方案是将事务ID作为一个参数,传到所有的业务方法中。但这并不是一个好的方案,它会使代码变得冗余。

你可以使用ThreadLocal类型的变量解决这个问题。首先在控制器或者任意一个预处理器拦截器中生成一个事务ID,然后在 ThreadLocal中 设置事务ID,最后,不论这个控制器调用什么方法,都能从threadlocal中获取事务ID。而且这个应用的控制器可以同时处理多个请求,同时在框架 层面,因为每一个请求都是在一个单独的线程中处理的,所以事务ID对于每一个线程都是唯一的,而且可以从所有线程的执行路径获取。

扩展阅读: 与JAX-RS ResteasyProviderFactory共享上下文数据(ThreadLocalStack实例)

ThreadLocal类

Java并发API为使用ThreadLocal类的局部线程变量提供了一个简洁高效的机制,

public class ThreadLocal<T> extends Object {...}

这个类提供了一个局部线程变量。这些变量不同于其所对应的常规变量,对于常规变量,每个线程只能访问(通过get或set方法)其自身所拥有的,独立初始化变量拷贝。在一个类中,ThreadLocal类型的实例是典型的 私有、静态 ( private static )字段,因为我们可以将其作为线程的关联状态(比如:用户ID或者事务ID)

这个类有以下方法:

  1. get() :返回当前线程拷贝的局部线程变量的值。
  2. initialValue() :返回当前线程赋予局部线程变量的初始值。
  3. remove() :移除当前线程赋予局部线程变量的值。
  4. set(T value) :为当前线程拷贝的局部线程变量设置一个特定的值。

怎样使用ThreadLocal?

下面的例子使用两个局部线程变量,即threadId和startDate。它们都遵循推荐的定义方法,即“private static”类型的字段。threadId用来区分当前正在运行的线程,startDate用来获取线程开启的时间。上面的信息将打印到控制台,以此验 证每一个线程管理他自己的变量拷贝。

class DemoTask implements Runnable {
  // Atomic integer containing the next thread ID to be assigned
  private static final AtomicInteger nextId = new AtomicInteger(0);
  // Thread local variable containing each thread‘s ID
  private static final ThreadLocal<Integer> threadId =
      new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
          return nextId.getAndIncrement();
        }
      };
  // Returns the current thread‘s unique ID, assigning it if necessary
  public int getThreadId() {
    return threadId.get();
  }
  // Returns the current thread‘s starting timestamp
  private static final ThreadLocal<Date> startDate =
     new ThreadLocal<Date>() {
        protected Date initialValue() {
          return new Date();
        }
     };
  @Override
  public void run() {
    System.out.printf("Starting Thread: %s : %sn",
                getThreadId(), startDate.get());
    try {
      TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.printf("Thread Finished: %s : %sn",
                getThreadId(), startDate.get());
  }
}

现在要验证变量本质上能够维持其自身状态,而与多线程的多次初始化无关。我们首先需要创建执行这个任务的三个线程,然后开启线程,接着验证它们打印到控制台中的信息。

Starting Thread: 0 : Wed Dec 24 15:04:40 IST 2014
Thread Finished: 0 : Wed Dec 24 15:04:40 IST 2014

Starting Thread: 1 : Wed Dec 24 15:04:42 IST 2014
Thread Finished: 1 : Wed Dec 24 15:04:42 IST 2014

Starting Thread: 2 : Wed Dec 24 15:04:44 IST 2014
Thread Finished: 2 : Wed Dec 24 15:04:44 IST 2014

在上面的输出中,打印出的声明序列每次都在变化。我已经把它们放到了序列中,这样对于每一个线程实例,我们都可以清楚地辨别出,局部线程变量保持着安全状态,而绝不会混淆。自己尝试下!

局部线程通常使用在这样的情况下,当你有一些对象并不满足线程安全,但是你想避免在使用synchronized关键字、块时产生的同步访问,那么,让每个线程拥有它自己的对象实例。

注意:局部变量是同步或局部线程的一个好的替代,它总是能够保证线程安全。唯一可能限制你这样做的是你的应用设计约束。

警告: 在webapp服务器上,可能会保持一个线程池,那么ThreadLocal变量会在响应客户端之前被移除,因为当前线程可能被下一个请求重复使用。而 且,如果在使用完毕后不进行清理,它所保持的任何一个对类的引用—这个类会作为部署应用的一部分加载进来—将保留在永久堆栈中,永远不会被垃圾回收机制回 收。

时间: 2024-08-12 12:27:04

帮助理解Java中ThreadLocal的一篇文章的相关文章

深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

转:http://zh.lucida.me/blog/java-8-lambdas-inside-out-library-features/ 关于 深入理解 Java 8 Lambda(语言篇--lambda,方法引用,目标类型和默认方法) 深入理解 Java 8 Lambda(类库篇--Streams API,Collector 和并行) 深入理解 Java 8 Lambda(原理篇--Java 编译器如何处理 lambda) 本文是深入理解 Java 8 Lambda 系列的第二篇,主要介绍

深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

最近看了一下java 8的一些新特性,其中最重要的莫过于Lambda表达式了,通过一阵子的学习,原本准备自己写一篇博客的,后来阅读了一位学长翻译过来的博客(原文是Brain Goetz的State of Lambda,下面会给出原文链接),觉得写的十分完美,把我想要写的和我没想到的都罗列了出来,就把这个分享给大家了. 注:原译文见  http://lucida.me/blog/java-8-lambdas-insideout-language-features/ 英语原版见:http://cr.

理解Java中的弱引用(Weak Reference)

理解Java中的弱引用(Weak Reference) 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出,谢谢大家:) 1. What——什么是弱引用? Java中的弱引用具体指的是java.lang.ref.WeakReference<T>类,我们首先来看一下官方文档对它做的说明: 弱引用对象的存在不会阻止它所指向的对象变被垃圾回收器回收.弱引

[转]深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-library-features 本文谢绝转载,如需转载需征得作者本人同意,谢谢. -------------------------------------内容分割线--------------------------------------------------------- 深入理解Java

理解java中的volatile关键字

Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了 实现代码线程的安全性.Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分. volatile 写和读的内存语义: 线程 A 写一个 volatile 变量,实质上是线程 A

深刻理解Java中形参与实参,引用与对象的关系

声明:本博客为原创博客,未经允许,不得转载!原文链接为http://blog.csdn.net/bettarwang/article/details/30989755 我们都知道,在Java中,除了基本数据类型之外,其他的都是引用类型,当它们作为函数参数时,传递的也是引用,通过引用可以改变对象的值,很多人便因此而忽略形参与实参,引用与对象的关系问题.废话不多说,先看下面一个例子: import java.util.*; public class Student { private String

深入理解Java中的IO

深入理解Java中的IO 引言:     对程序语言的设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的任务 < Thinking in Java >   本文的目录视图如下: Java IO概要 a.Java IO中常用的类 b.Java流类的类结构图 1.流的概念和作用 2.Java IO所采用的模型  : 3.IO流的分类 4.Java IO流对象 1.输入字节流InputStream 2.输出字节流OutputStream 3.字符输入流Reader 4.字符输出流Write

深刻理解Java中final的作用(一):从final的作用剖析String被设计成不可变类的深层原因

声明:本博客为原创博客,未经同意,不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(原文链接为http://blog.csdn.net/bettarwang/article/details/26744661),看代码和提问.讨论都更方便. Java中final的作用主要表如今三方面:修饰变量.修饰方法和修饰类.以下就从这两个方面来解说final的作用.在文末从final及类的设计安全性出发,论述了Java中String为何要被设计成不可变类. 1.final修饰变量 fina

线程的生命周期 - 理解Java中线程的状态

刚刚开始学cocos2-x,仅仅是按照教程把已经安了一般Android的开发环境的eclipse重新升级到安装好cdt和ndk就花了我几十小时,差点都要放弃了. 参考博客 D:\cocos2d-x\cocos2d-x-2.2.3\cocos2dx\platform\third_party\android\prebuilt 说说大概的过程: 下载ndk插件,ndk包,cdt插件.最开始我按照书上的下载了cocos2d-x 2.0.1,希望跟书上统一起来,这样以后学习的时候可以参考书上的也不会遇到太