首页 > 代码库 > ThreadLocal的使用

ThreadLocal的使用

在51CTO看到的一篇关于ThreadLocal的博客,前半部分写得很好,后面举的Student根本就没有意义,不同的线程拿到当前的Student,每个线程打印出来的肯定是自己Student的age变量。这个和ThreadLocal作用没有关系。只好再去Google找找。


发现了一篇写得比较好的博客,已邮件联系作者并得到作者同意(印度开发也挺厉害的,之前看到Jakod Jenkov写的并发的文章也很nice,也准备翻译,询问之后他说已经有网站翻译了他大部分的文章了,真是巧)。


原文地址:http://veerasundar.com/blog/2010/11/java-thread-local-how-to-use-and-code-sample/


Java Thread Local —— 如何使用和事例代码


Thread Local是一个有趣并且有用的概念,但是大多数的Java开发者并不知道如何使用(是的,我自己当初面试的时候从见过这个词)。在这里,我会用代码例子介绍它是什么和什么时候用它。


因为刚开始理解它的概念有点难度,我尽可能简单地解释(结论:你不应该在开发项目的时候使用这些代码,要捉住概念并在此基础上修改!)


让我们开始。


什么是Thread Local?

Thread Local可以看成是一个访问域(scope of acess),像请求域或者是session域。它是一个线程域。你可以在Thread Local中设置任意的对象,那么这个对象将在访问这个对象的线程中既是全局又是局部。全局和局部!!?

让我解释一下:

存储在Thread Local的值在线程中是全局的,意味着他们可以在线程内部的任意位置都可以访问到。如果一个线程调用了多个类的方法,那么任意方法都可以看到其他方法在Thread Local设置的变量(因为他们在同一个线程中)。这个值不需要被显式地传递。这就像你在使用全局变量一样。


存储在Thread Local的值在线程中是局部的,意味着每一个线程拥有自己的Thread Local变量,一个线程不能访问和修改其他线程的Thread Local变量。


好了,这就是Thread Local的概念了,希望你理解了。


什么时候使用Thread Local

我们看了前面部分讲的什么是Thread Local。现在让我们讨论一下你需要用到Thread Local的事例。


我展示一个用到Thread Local的用例。想一下,你有一个Servlet调用了一些业务方法。你需要为对该servlet的每个请求过程生成一个唯一的事务id,你需要将这个事务id传递到业务方法中。但这不是一种好的解决方法,代码冗余且没必要。


解决办法是你可以使用Thread Local,你可以生成一个事务id(可以再servlet中,最好在过滤器中生成),然后把它设进Thread Local中。接下来,无论是哪个servlet调用的业务方法,都可以从Thread Local中访问到业务id。


这个servlet可能每次服务于多个请求,因为每个请求由单独的线程处理,业务id对于每个线程来说是唯一的(局部),业务id对于线程的执行是可访问的(全局)。

(翻译者的看法,生成的事务id交给Thread Local处理,每个线程都有自己的业务id,针对属于自己的线程来说,业务id对自己线程里面的操作又是可见的。)


理解了?!


如何使用Thread Local

Java提供了Thread Local类,你可以用它来set/get 线程域的变量。下面用代码示例来展示我前面说的那些。


Context.java,持有transactionid域。

public class Context {
    private String transactionId = null;
	public String getTransactionId() {
		return transactionId;
	}
	public void setTransactionId(String transactionId) {
		this.transactionId = transactionId;
	}
}


MyThreadLocal.java,作为持有Context对象的容器。

/**
 * this class acts as a container to our thread local variables.
 * @author vsundar
 *
 */
public class MyThreadLocal {

    public static final ThreadLocal userThreadLocal = new ThreadLocal();

    public static void set(Context user) {
        userThreadLocal.set(user);
    }

    public static void unset() {
        userThreadLocal.remove();
    }

    public static Context get() {
        return (Context) userThreadLocal.get();
    }
}


上面代码中,在静态域里面创建了ThreadLocal类,这样在代码的其他部分就可以set/getThread Local变量。


创建main类,在thread local生成和设置transaction ID ,调用业务方法。

public class ThreadLocalDemo extends Thread {

    public static void main(String args[]) {
    	Thread threadOne = new ThreadLocalDemo();
    	threadOne.start();
        Thread threadTwo = new ThreadLocalDemo();
        threadTwo.start();
    }

    @Override
    public void run() {
        // sample code to simulate transaction id
        Context context = new Context();
        context.setTransactionId(getName());
        // set the context object in thread local to access it somewhere else
        MyThreadLocal.set(context);
        /* note that we are not explicitly passing the transaction id */
        new BusinessService().businessMethod();
        MyThreadLocal.unset();

    }
}


最后,BusinessService.java可以读取Thread Local的值并使用。

public class BusinessService {

    public void businessMethod() {
        // get the context from thread local
        Context context = MyThreadLocal.get();
        System.out.println(context.getTransactionId());
    }
}


最后输出:

Thread-0
Thread-1


你会看到,我们虽然没有显式地传递transaction id的值,值可以在business方法中访问到并且在控制台输出。而且,transaction id在每个线程中都不同(0和1)。


好了,就是这些了,我尽可能简单地解释它,你可以留下评论描述你的看法,如果你想为这个主题添加任何的东西,也留下你的评论。


译者继续写点东西,可能一路看下来你又会模糊了,你会思考什么全局局部在这个例子怎么没有?所谓transaction id传递不传递有什么用?


我再补全:

再写一个

public class BusinessServiceTwo {

    public void businessMethod() {
        // get the context from thread local
        Context context = MyThreadLocal.get();
        System.out.println("the second service is"+context.getTransactionId());
    }
}

MyThreadDemo的run方法里面也写多一个方法

new BusinessServiceTwo().businessMethod();

这样就好理解了,所谓的局部,任意线程都不能看到其他线程的transaction id是什么。

所谓全局,同一个线程调用的不同业务逻辑,但是,他们的transaction id是一样的,全局。


那有什么用呢?

设想,如果我是银行,我要知道是谁在取钱,如果每次我要知道是谁在操作业务那么

我写的就是service(String name),service2(String name),这样是不是冗余了,而且也不准确,哪天我要把其他参数也传进去,是不是烦死了?


所以ThreadLocal来了。把这些属性放到一个类中,Thread存进这个类,每次业务逻辑调用的时候只需要

Context context = MyThreadLocal.get();

这样是不是简洁好多?


博客写得确实好,我再看一次之后,也真正弄懂了。


完。

ThreadLocal的使用