Java常用类库之ThreadLocal类
在项目开发过程之中,ThreadLocal是一个最为重要的引用数据类型的传递操作类,利用这个类可以非常轻松的实现数据的传输,同时也可以保证多个线程下的数据传输的正确性,由于ThreadLocal对于很多的初学者来说很难理解,所以下面就针对与当前给出的操作进行一些递进分析。
范例: 观察传统的引用传递
class Message{//信息的操作类
private String content;//类中的属性
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
class MessagePrint{ //信息打印
public static void print(Message msg ){
System.out.println("【MessagePrint】"+msg.getContent());
}
}
public class Demo {
public static void main(String[] args) {
Message msg = new Message();
msg.setContent("www.baidu.com");//内容的设置
MessagePrint.print(msg);
}
}
执行结果:
【MessagePrint】www.baidu.com
Java本身属于引用数据类型,那么既然属于引用数据类型,就可以实现引用传递,而引用传递的本质就在于同一块堆内存空间,可以同时被多个栈内存指向。
但是后来发现由于某种原因,不希望MessagePrint类的print()方法接收Message对象引用,但是又要求可以实现Message对象内部中的content属性内容的输出,所以这个时候就可以考虑引入一个中间的数据存储类 ——Resource。
范例: 观察程序实现
class Message {//信息的操作类
private String content;//类中的属性
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
class MessagePrint { //信息打印
public static void print() {
System.out.println("【MessagePrint】" + Resource.message.getContent());
}
}
class Resource {//中间类
public static Message message;
}
public class Demo {
public static void main(String[] args) {
Resource.message = new Message();
Resource.message.setContent("www.baidu.com");//内容的设置
MessagePrint.print();//不再传递引用对象
}
}
执行结果:
【MessagePrint】www.baidu.com
当前的代码已经完成了MessagePrint()方法不接收Message实例化对象的处理操作,从某种程度上来讲当前的问题已经解决了,但是现在的代码是在单线程的状态下进行的,如果现在采用的是多线程模式呢?
范例: 观察多线程模式下的程序问题
class Message {//信息的操作类
private String content;//类中的属性
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
class MessagePrint { //信息打印
public static void print() {
System.out.println("【MessagePrint】" + Resource.message.getContent());
}
}
class Resource {//中间类
public static Message message;
}
public class Demo {
public static void main(String[] args) {
String [] values = {"百度","腾讯","阿里"};//每个线程执行各自的内容输出
for (String msg : values) {
new Thread(()->{
Resource.message = new Message();
Resource.message.setContent(msg);//内容的设置
MessagePrint.print();//不再传递引用对象
}).start();
}
}
}
执行结果(这里只是其中一种结果):
【MessagePrint】腾讯
【MessagePrint】腾讯
【MessagePrint】腾讯
通过多线程的执行分析就可以发现当前程序设计的问题了,现在原本设计有三个线程对象,希望的处理形式是可以每一个线程都执行各自的信息输出,但是最终执行结果却完全不正确了,那么到底是什么原因呢?
所以通过分析就可以发现,当前程序之所以出现问题,是在于Resource只能够保存有一个message对象的实例,如果此时启动了多个线程,那么就有可能在还没有来得及输出的情况下,Resource中的message就已经发生了变更,所以这三个线程任何的信息都有可能重复输出三次,而要想解决这个问题,就需要在ThreadLocal之中引入更多的机制。
想办法:把不同线程的内容分开存储,每一个线程都以自己的对象作为获取数据的key,那么这样就可以避免这种同步的问题了。
为了解决这种公共资源数据存储的问题,在java.lang包里面提供有一个ThreadLocal程序类,这个类中存在有如下的方法:
序号 | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public void set(T value) | 普通 | 保存数据,每一个线程只能保存一个数据 |
2 | public T get() | 普通 | 获取当前线程保存的数据 |
3 | public void remove() | 普通 | 清除当前线程的数据 |
范例: 通过ThreadLocal解决当前的多线程并发访问设计问题
class Message {//信息的操作类
private String content;//类中的属性
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
class MessagePrint { //信息打印
public static void print() {
//获取当前线程所保存的Message对象
System.out.println("【MessagePrint】" + Resource.MESSAGES.get().getContent());
}
}
class Resource {//中间类
public static final ThreadLocal<Message> MESSAGES = new ThreadLocal<>();
}
public class Demo {
public static void main(String[] args) {
String [] values = {"百度","腾讯","阿里"};//每个线程执行各自的内容输出
for (String msg : values) {
new Thread(()->{
Resource.MESSAGES.set(new Message());
Resource.MESSAGES.get().setContent(msg);//内容的设置
MessagePrint.print();//不再传递引用对象
}).start();
}
}
}
执行结果:
【MessagePrint】阿里
【MessagePrint】百度
【MessagePrint】腾讯
此程序直接利用了ThreadLocal实现了多个线程对象不同的数据存储,由于所有的存储都是以当前对象的形式作为了key,那么只要当前对象有数据就可以直接获取数据并且实现引用对象的传输。在实际的项目开发过程里面,一般使用ThreadLocal的代码都属于核心的资源对象传输。
注:本文整理自【李兴华编程训练营】,老师的讲解很棒!附上视频资源链接[https://www.bilibili.com/video/BV147411j7ei?p=12]