通俗的来讲,ThreadLocal主要作用就是让各自线程自能取得各自线程里面的东西,也即是各自线程只能忙活自己的事,管不了别人的事,通过下面的Demo来看一下ThreadLocal是否能实现这个功能
ThreadLocal类就像Map,List这种类一样也是用来存储数据的,只是ThreadLocal类主要是为了线程数据存储而生的,目的是保证每一个线程只能存储和取出自己的东西。另外这个类是带泛型的,你想使用ThreadLocal存什么数据类型就要在泛型里面是用什么泛型参数。
public class TestDemo{
//这里为什么使用static关键字进行修饰,自己去想,不解释。
private static ThreadLocal<String> sThreadLocal=new ThreadLocal<>();
public static void main(String args[]) {
//主线程
sThreadLocal.set("这是在主线程中");
System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+sThreadLocal.get());
//线程a
new Thread(new Runnable() {
public void run() {
sThreadLocal.set("这是在线程a中");
System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+sThreadLocal.get());
}
},"线程a").start();
//线程b
new Thread(new Runnable() {
public void run() {
sThreadLocal.set("这是在线程b中");
System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+sThreadLocal.get());
}
},"线程b").start();
//线程c 这个线程是使Lambda建立的,跟上面两个是一样的。不会使用Lambda表达式的同学请看我的另一篇博客
new Thread(()->{
sThreadLocal.set("这是在线程c中");
System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+sThreadLocal.get());
},"线程c") .start();
}
}
输出如下:
线程名字:main---这是在主线程中
线程名字:线程a---这是在线程a中
线程名字:线程b---这是在线程b中
线程名字:线程c---这是在线程c中
看完这个程序是不是很清楚了。我们开启了三个线程,加上主线程就是四个线程,每个线程都set了一句话,然后使用get取出,他们各自取出各自的设置内容,并没有取出别的线程的内容。
下面我们通过一个实例加深对ThreadLocal类的实际用,让我们看看真正开发之中是怎么使用ThreadLocal类的。
在讲解ThreadLocal类之前首先我们先来看一下如下程序:
class Message{
private String note;
public void setNote(String note) {
this.note=note;
}
public String getNote() {
return this.note;
}
}
class MessageConsumer{
public void print(Message msg) {
System.out.println(Thread.currentThread().getName()+msg.getNote());
}
}
public class TestDemo{
public static void main(String args[]) {
new Thread(()->{
Message msgA=new Message();
msgA.setNote("中国矿业大学北京");
new MessageConsumer().print(msgA);
},"学生A") .start();
new Thread(()->{
Message msgB=new Message();
msgB.setNote("清华大学");
new MessageConsumer().print(msgB);
},"学生B").start();
}
}
以上程序输出为
学生B清华大学
学生A中国矿业大学北京
(注释:上面的程序使用了La'mbda表达式,不知道Lambda表达式的同学可以参考我的另一篇博客。Lambda表达式还是很重要的,希望大家能够能弄懂。)
当然,这个程序的输出是完全正确的。但是现在我有一个这样的需求:我不想把设置好数据的Message对象传给MessageConsumer的print()方法了,但是要求是还是能正常输出以上结果,那又怎么办?
我们的思路是:新建一个类里面有一个静态的Message对象,拿着这个对象进行操作就行了,现在我们就来看看代码的具体实现:
class Message{
private String note;
public void setNote(String note) {
this.note=note;
}
public String getNote() {
return this.note;
}
}
class MessageConsumer{
public void print() {
System.out.println(Thread.currentThread().getName()+MyUtil.msg.getNote());
}
}
class MyUtil{
public static Message msg;
}
public class TestDemo{
public static void main(String args[]) {
new Thread(()->{
Message msgA=new Message();
msgA.setNote("中国矿业大学北京");
MyUtil.msg=msgA;
new MessageConsumer().print();
},"学生A") .start();
new Thread(()->{
Message msgB=new Message();
msgB.setNote("清华大学");
MyUtil.msg=msgB;
new MessageConsumer().print();
},"学生B").start();
}
}
以上程序的输出:
学生A中国矿业大学北京
学生B中国矿业大学北京
或者
学生B清华大学
学生A清华大学
看似很完美的修改为什么会有如此的输出呢?因为当设置“中国矿业大学北京”的时候,学生A(线程名字,相同的下面的学生B也是一个认为的起的线程名)的msgA被赋值给MyUtil的静态变量mag,但是当学生A线程刚执行完语句MyUtil.msg=msgA,这个线程就停止执行了,把资源让给学生B线程执行(至于那个线程执行,这个是随机的)B线程执行完MyUtil.msg=msgB这条语句还没有停下来而是继续执行输出语句new MessageConsumer().print();当B线程执行完这条语句的时候还没有把资源出让给A,而是继续执行了赋值语句,再次把公共的msg设置成了“清华大学”,当设置完之后,也就是B再次执行完了MyUtil.msg=msgB一句之后,A线程恰好获得了资源,接着就执行了输出语句new MessageConsumer().print();可悲的是这个时候msg已经不再是当初的“中国矿业大学北京”了,而是“清华大学”,所以也就输出了“清华大学”。这就是对出错原理进行的分析。问题也就因此而出现了。那么,怎么才能解决以上出现的问题呢?
解决这个问题也简单,思路就是:让线程设置之后,取出的也必须是设置自己的那个线程就行了。而ThreadLocal类就是解决这个问题的。下面来看一下ThreadLocal类的定义:
public class ThreadLocal<T> extends Object{}
下面是ThreadLocal类主要的方法:
public void set(T value)//存一个数据
public T get()//取一个数据
现在使用ThreadLocal来修改以上出现问题的代码,如下:
class Message{
private String note;
public void setNote(String note) {
this.note=note;
}
public String getNote() {
return this.note;
}
}
class MessageConsumer{
public void print() {
System.out.println(Thread.currentThread().getName()+MyUtil.get().getNote());
}
}
class MyUtil{
private static ThreadLocal<Message> threadLocal=new ThreadLocal<>();
public static void set(Message msg) {
threadLocal.set(msg);
}
public static Message get() {
return threadLocal.get();
}
}
public class TestDemo{
public static void main(String args[]) {
new Thread(()->{
Message msgA=new Message();
msgA.setNote("中国矿业大学北京");
MyUtil.set(msgA);
new MessageConsumer().print();
},"学生A") .start();
new Thread(()->{
Message msgB=new Message();
msgB.setNote("清华大学");
MyUtil.set(msgB);
new MessageConsumer().print();
},"学生B").start();
}
}
输出结果:
学生A中国矿业大学北京
学生B清华大学
完美解决,这就是ThreadLocal的主要用途。