深入理解ThreadLocal类+Demo详细解析

 

通俗的来讲,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的主要用途。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值