ThreadLocal学习笔记
说明:代码来自《阿里公开课》
一、ThreadLocal是什么
ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全。
ThreadLocal 的使用:
static final ThreadLocal<T> sThreadLocal = new ThreadLocal<T>(); sThreadLocal.set() sThreadLocal.get()
二、代码演示ThreadLocal的作用
场景说明:
1. 定义一个消息实体MessageEntity,用来作为消息传递的载体;
2. 定义个发送消息的工具/通道Channel,用来发送消息;
3. 定义一个main方法,创建多个线程,来验证测试;
消息实体:
public class MessageEntity { private String message ; public MessageEntity(String message) { this.message = message; } public String getMessage() { return message; } }
消息发送工具/通道
class Channel{ // 这是一个静态变量(属于该类的全局变量) private static MessageEntity messageEntity; public static void setMessageEntity(MessageEntity message){ messageEntity = message; } public static void sendMessage(){ System.out.println("["+Thread.currentThread().getName()+"消息发送:]" + messageEntity.getMessage());; } }
定义主方法
public class ThreadLocalDemo { public static void main(String[] args){ Thread thread1 = new Thread(new Runnable() { @Override public void run() { Channel.setMessageEntity(new MessageEntity("这是线程1的消息")); Channel.sendMessage(); } },"线程1"); Thread thread2 = new Thread(new Runnable() { @Override public void run() { Channel.setMessageEntity(new MessageEntity("这是线程2的消息")); Channel.sendMessage(); } },"线程2"); Thread thread3 = new Thread(new Runnable() { @Override public void run() { Channel.setMessageEntity(new MessageEntity("这是线程3的消息")); Channel.sendMessage(); } },"线程3"); thread1.start();thread2.start();thread3.start(); } }
执行效果:
由于消息发送的通道的MessageEntity属性是被static修饰的,说明它是类成员变量,所以在某个线程修改了这个成员变量的值,在执行发送之前Channel.sendMessage(),被其他线程修改了,就会导致下面的结果,当前线程发送了别的线程的消息;这就产生了并发的数据问题;
private static MessageEntity messageEntity;
使用ThreadLocal改进:
在不改变预定结构的情况下,我们可以使用ThreadLocal来避免这种并发问题,将类成员变量拷贝到各自的线程里;
1. 将MessageEntity 使用ThreadLocal的方式进行声明;
2. 使用set方法复制副本;
3. 使用get方法得到副本;
class Channel{ private static final ThreadLocal<MessageEntity> MESSAGE_LOCAL = new ThreadLocal<>(); public static void setMessageEntity(MessageEntity message){ MESSAGE_LOCAL.set(message); } public static void sendMessage(){ System.out.println("["+Thread.currentThread().getName()+"消息发送:]" + MESSAGE_LOCAL.get().getMessage());; } }
修改后每次执行的效果都是正确的:
三、其他补充:
1. 必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally块进行回收。《阿里巴巴开发手册》
objectThreadLocal.set(userInfo); try { // ... } finally { objectThreadLocal.remove(); }
2. ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是:
- Synchronized是通过线程等待,牺牲时间来解决访问冲突
- ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。