了解ThreadLocal背后的概念

介绍

我知道本地线程,但直到最近才真正使用过它。
因此,我开始深入研究该主题,因为我需要一种传播某些用户信息的简便方法
通过Web应用程序的不同层,而无需更改每个调用方法的签名。

小前提信息

线程是具有自己的调用栈的单个进程。在Java中,每个调用栈有一个线程,或者每个线程有一个调用栈。即使您没有在程序中创建任何新线程,线程也可以在没有您的程序的情况下运行最好的例子是当您仅通过main方法启动一个简单的Java程序时,您没有隐式调用new Thread()。start(),而是JVM为您创建了一个主线程来运行main方法。

主线程是非常特殊的,因为它是所有其他线程都会从中生成的线程,
线程完成后,应用程序结束了它的生命周期。

在Web应用程序服务器中,通常会有一个线程池,因为要创建的线程类非常重。所有JEE服务器(Weblogic,Glassfish,JBoss等)都有一个自调整线程池,这意味着线程池会增加或减少需要的时间,因此不会在每个请求上创建线程,而现有的线程将被重用。

了解线程局部

为了更好地理解线程本地,我将展示一种自定义线程本地的非常简单的实现。

package ccs.progest.javacodesamples.threadlocal.ex1;

import java.util.HashMap;
import java.util.Map;

public class CustomThreadLocal {

 private static Map threadMap = new HashMap();

 public static void add(Object object) {
  threadMap.put(Thread.currentThread(), object);
 }

 public static void remove(Object object) {
  threadMap.remove(Thread.currentThread());
 }

 public static Object get() {
  return threadMap.get(Thread.currentThread());
 }

}

因此,您可以随时在应用程序中调用CustomThreadLocal上的add方法, 它将把当前线程作为并将要与该线程关联的对象作为值放入映射中 。 该对象可能是您希望从当前执行的线程中的任何位置访问的对象,或者可能是您想要与该线程保持关联并重复使用多次的昂贵对象。
您定义一个ThreadContext类,您在其中拥有要在线程内传播的所有信息。

package ccs.progest.javacodesamples.threadlocal.ex1;

public class ThreadContext {

 private String userId;

 private Long transactionId;

 public String getUserId() {
  return userId;
 }

 public void setUserId(String userId) {
  this.userId = userId;
 }

 public Long getTransactionId() {
  return transactionId;
 }

 public void setTransactionId(Long transactionId) {
  this.transactionId = transactionId;
 }

 public String toString() {
  return 'userId:' + userId + ',transactionId:' + transactionId;
 }

}

现在是时候使用ThreadContext了。

我将启动两个线程,并在每个线程中添加一个新的ThreadContext实例,该实例将保存我想为每个线程传播的信息。

package ccs.progest.javacodesamples.threadlocal.ex1;

public class ThreadLocalMainSampleEx1 {

 public static void main(String[] args) {
  new Thread(new Runnable() {
   public void run() {
    ThreadContext threadContext = new ThreadContext();
    threadContext.setTransactionId(1l);
    threadContext.setUserId('User 1');
    CustomThreadLocal.add(threadContext);
    //here we call a method where the thread context is not passed as parameter
    PrintThreadContextValues.printThreadContextValues();
   }
  }).start();
  new Thread(new Runnable() {
   public void run() {
    ThreadContext threadContext = new ThreadContext();
    threadContext.setTransactionId(2l);
    threadContext.setUserId('User 2');
    CustomThreadLocal.add(threadContext);
    //here we call a method where the thread context is not passed as parameter
    PrintThreadContextValues.printThreadContextValues();
   }
  }).start();
 }
}

注意:
CustomThreadLocal.add(threadContext)是当前线程与ThreadContext实例相关联的代码行
您将看到执行此代码,结果将是:

userId:User 1,transactionId:1
userId:User 2,transactionId:2

这是怎么可能的,因为我们没有将ThreadContext,userId或trasactionId作为参数传递给printThreadContextValues?

package ccs.progest.javacodesamples.threadlocal.ex1;

public class PrintThreadContextValues {
 public static void printThreadContextValues(){
  System.out.println(CustomThreadLocal.get());
 }
}

很简单

从CustomThreadLocal的内部映射调用CustomThreadLocal.get()时,将检索与当前线程关联的对象。

现在,让我们看看何时使用真正的ThreadLocal类的示例。 (上面的CustomThreadLocal类只是为了了解ThreadLocal类背后的原理,该原理非常快并以最佳方式使用内存)

package ccs.progest.javacodesamples.threadlocal.ex2;

public class ThreadContext {

 private String userId;
 private Long transactionId;

 private static ThreadLocal threadLocal = new ThreadLocal(){
  @Override
        protected ThreadContext initialValue() {
            return new ThreadContext();
        }

 };
 public static ThreadContext get() {
  return threadLocal.get();
 }
 public String getUserId() {
  return userId;
 }
 public void setUserId(String userId) {
  this.userId = userId;
 }
 public Long getTransactionId() {
  return transactionId;
 }
 public void setTransactionId(Long transactionId) {
  this.transactionId = transactionId;
 }

 public String toString() {
  return 'userId:' + userId + ',transactionId:' + transactionId;
 }
}

javadoc所述:ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段。

package ccs.progest.javacodesamples.threadlocal.ex2;

public class ThreadLocalMainSampleEx2 {

 public static void main(String[] args) {
  new Thread(new Runnable() {
   public void run() {
    ThreadContext threadContext = ThreadContext.get();
    threadContext.setTransactionId(1l);
    threadContext.setUserId('User 1');
    //here we call a method where the thread context is not passed as parameter
    PrintThreadContextValues.printThreadContextValues();
   }
  }).start();
  new Thread(new Runnable() {
   public void run() {
    ThreadContext threadContext = ThreadContext.get();
    threadContext.setTransactionId(2l);
    threadContext.setUserId('User 2');
    //here we call a method where the thread context is not passed as parameter
    PrintThreadContextValues.printThreadContextValues();
   }
  }).start();
 }
}

调用get时 ,新的ThreadContext实例与当前线程关联,然后将所需的值设置为ThreadContext实例。

如您所见,结果与第一组样本相同。

userId:User 1,transactionId:1
userId:User 2,transactionId:2

(这可能是相反的顺序,所以不要担心如果您首先看到“用户2”?)

package ccs.progest.javacodesamples.threadlocal.ex2;

public class PrintThreadContextValues {
 public static void printThreadContextValues(){
  System.out.println(ThreadContext.get());
 }
}

ThreadLocal的另一种非常有用的用法是当您有一个非常昂贵的对象的非线程安全实例时的情况。我发现的大多数极性示例是使用SimpleDateFormat(但很快我将提供另一个使用Webservices端口的示例)

package ccs.progest.javacodesamples.threadlocal.ex4;

import java.text.SimpleDateFormat;
import java.util.Date;

public class ThreadLocalDateFormat {
 // SimpleDateFormat is not thread-safe, so each thread will have one
 private static final ThreadLocal formatter = new ThreadLocal() {
  @Override
  protected SimpleDateFormat initialValue() {
   return new SimpleDateFormat('MM/dd/yyyy');
  }
 };
 public String formatIt(Date date) {
  return formatter.get().format(date);
 }
}

结论:

线程局部变量有很多用途,这里仅描述两种:(我认为使用最多的)

  • 真正的每线程上下文,例如用户ID或事务ID。
  • 每线程实例以提高性能。

参考: Java代码样本博客中的JCG合作伙伴 Cristian Chiovari 了解了ThreadLocal的概念


翻译自: https://www.javacodegeeks.com/2012/07/understanding-concept-behind.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值