JUC- ThreadLocal学习笔记

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;

 

JUC- ThreadLocal学习笔记

使用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());;
    }
}

修改后每次执行的效果都是正确的:

JUC- ThreadLocal学习笔记 

 

三、其他补充:

1. 必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally块进行回收。《阿里巴巴开发手册》

objectThreadLocal.set(userInfo);
try {
    // ...
} finally {
    objectThreadLocal.remove();
}

 

2. ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是:

  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。


 

上一篇:退后!我要开始装逼了!头条二面居然能倒在一个小小的ThreadLocal?


下一篇:ThreadLocal(笔记)