• Java线程变量问题-ThreadLocal


    关于Java线程问题,在博客上看到一篇文章挺好的:

    https://blog.csdn.net/w172087242/article/details/83375022#23_ThreadLocal_175

    自己动手实验了一下。

    1、maven设置

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.4</version>
    <scope>provided</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.alibaba/transmittable-thread-local -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.10.2</version>
    </dependency>

     

    2、目录设置

    3、公共服务类

    ①:用户实体类

    package cn.demo.entity;

    import java.time.LocalDate;

    import lombok.Data;
    import lombok.ToString;

    @Data
    @ToString
    public class User {

    private Integer userId;
    private String name;
    private LocalDate birthday;

    public Integer getUserId() {
    return userId;
    }

    public void setUserId(Integer userId) {
    this.userId = userId;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public LocalDate getBirthday() {
    return birthday;
    }

    public void setBirthday(LocalDate birthday) {
    this.birthday = birthday;
    }

    @Override
    public String toString() {
    return "User [userId=" + userId + ", name=" + name + ", birthday=" + birthday + "]";
    }
    }

    ②:用户信息管理上下文类

    package cn.demo.context;

    import cn.demo.entity.User;

    /**
    * 基于线程上下文的用户信息管理
    */
    public class BaseUserContext {

    //存储线程变量
    public ThreadLocal<User> context = null;

    /**
    * 设置用户信息
    *
    * @param user -- 用户信息
    */
    public void set(User user) {
    context.set(user);
    }

    /**
    * 获取用户信息
    *
    * @return -- 用户信息
    */
    public User get() {
    return context.get();
    }

    /**
    * 移除用户信息
    */
    public void remove() {
    context.remove();
    }
    }

    ③:基本调用服务类(子类继承)

    package cn.demo.context;

    import cn.demo.entity.User;

    /**
    * 基于线程上下文的用户信息管理
    */
    public class BaseUserContext {

    //存储线程变量
    public ThreadLocal<User> context = null;

    /**
    * 设置用户信息
    *
    * @param user -- 用户信息
    */
    public void set(User user) {
    context.set(user);
    }

    /**
    * 获取用户信息
    *
    * @return -- 用户信息
    */
    public User get() {
    return context.get();
    }

    /**
    * 移除用户信息
    */
    public void remove() {
    context.remove();
    }
    }

    ④:接口服务

    package cn.demo.service;

    import cn.demo.context.BaseUserContext;

    public class UserService {

    private BaseUserContext userContext;

    public UserService(BaseUserContext userContext) {
    this.userContext = userContext;
    }

    /**
    * 执行添加用户
    */
    public void addUser() {
    System.out.println(Thread.currentThread().getName() + "添加用户信息:" + userContext.get());
    }
    }

    4、ThreadLocal,线程变量

    优点:多线程环境中存储线程级别变量,单线程没有必要使用。

    代码-上下文:

    package cn.demo.context;

    import cn.demo.entity.User;

    public class UserContext1 extends BaseUserContext {

    public UserContext1() {
    //1、线程开启新线程有缺陷
    this.context = new ThreadLocal<User>();
    }
    }

    代码-调用:

    package cn.demo.call;

    import cn.demo.context.BaseUserContext;
    import cn.demo.context.UserContext1;
    import cn.demo.service.UserService;

    public class CallService1 extends BaseCall {
    public static void main(String[] args) {
    BaseUserContext userContext = new UserContext1();
    UserService userService = new UserService(userContext);
    //同时10个调用
    for (int i = 0; i < 10; i++) {
    new Thread(() -> {
    userContext.set(initUser(Thread.currentThread().getName()));
    //进行调用
    userService.addUser();
    }, "CallService1-" + i).start();
    }
    }

    }

    控制台输出结果:(正确)

    CallService1-3添加用户信息:User [userId=3, name=CallService1-3, birthday=1995-07-26]
    CallService1-8添加用户信息:User [userId=4, name=CallService1-8, birthday=2000-10-01]
    CallService1-2添加用户信息:User [userId=8, name=CallService1-2, birthday=1995-07-26]
    CallService1-5添加用户信息:User [userId=9, name=CallService1-5, birthday=2000-10-01]
    CallService1-7添加用户信息:User [userId=10, name=CallService1-7, birthday=1988-09-11]
    CallService1-1添加用户信息:User [userId=6, name=CallService1-1, birthday=1989-11-10]
    CallService1-4添加用户信息:User [userId=7, name=CallService1-4, birthday=1990-03-07]
    CallService1-9添加用户信息:User [userId=5, name=CallService1-9, birthday=1988-09-11]
    CallService1-0添加用户信息:User [userId=1, name=CallService1-0, birthday=1989-11-10]
    CallService1-6添加用户信息:User [userId=2, name=CallService1-6, birthday=1990-03-07]

    缺点:它仅仅能获取自己当前线程设置的变量,开启新的线程后获取到初始线程设置的变量值。

    代码-调用:

    package cn.demo.call;

    import cn.demo.context.BaseUserContext;
    import cn.demo.context.UserContext1;
    import cn.demo.service.UserService;

    public class CallService2 extends BaseCall {

    public static void main(String[] args) {
    //main作为当前调用线程
    BaseUserContext userContext = new UserContext1();
    userContext.set(initUser(Thread.currentThread().getName()));
    UserService userService = new UserService(userContext);
    //开启新线程来进行调用
    new Thread(() -> userService.addUser(), "CallService2").start();
    }

    }

    控制台输出结果:(错误)

    CallService2添加用户信息:null 

    5、InheritableThreadLocal

    解决开启新的线程后,ThreadLocal无法获取到线程变量问题。

    但是在应用线程池的场景中,线程复用导致读取线程变量数据混乱问题(真实项目中线程池应用很广泛)

    代码-上下文:

    package cn.demo.context;

    import cn.demo.entity.User;

    public class UserContext3 extends BaseUserContext {
    public UserContext3() {
    //2、线程复用导致数据混乱
    this.context = new InheritableThreadLocal<User>();
    }
    }

    代码-调用:

    package cn.demo.call;

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;

    import cn.demo.context.BaseUserContext;
    import cn.demo.context.UserContext3;
    import cn.demo.service.UserService;

    public class CallService3 extends BaseCall {

    //申明一个简单的线程池,3个核心线程
    private static final AtomicInteger threadIdCreator = new AtomicInteger(1);
    private static ExecutorService pool = Executors.newFixedThreadPool(3, (runnable) ->
    new Thread(runnable, "ThreadName-" + threadIdCreator.getAndIncrement())
    );

    public static void main(String[] args) {
    BaseUserContext userContext = new UserContext3();
    UserService userService = new UserService(userContext);
    //同时10个调用
    for (int i = 0; i < 10; i++) {
    new Thread(() -> {
    userContext.set(initUser(Thread.currentThread().getName()));
    //使用线程池进行调用
    pool.execute(userService::addUser);
    }, "CallService3-" + i).start();
    }
    }

    }

    控制台输出结果:(错误:复用线程导致线程变量混乱,只有用户1,2,3)

    ThreadName-2添加用户信息:User [userId=3, name=CallService3-4, birthday=1995-07-26]
    ThreadName-3添加用户信息:User [userId=2, name=CallService3-1, birthday=1990-03-07]
    ThreadName-3添加用户信息:User [userId=2, name=CallService3-1, birthday=1990-03-07]
    ThreadName-1添加用户信息:User [userId=1, name=CallService3-0, birthday=1989-11-10]
    ThreadName-3添加用户信息:User [userId=2, name=CallService3-1, birthday=1990-03-07]
    ThreadName-3添加用户信息:User [userId=2, name=CallService3-1, birthday=1990-03-07]
    ThreadName-2添加用户信息:User [userId=3, name=CallService3-4, birthday=1995-07-26]
    ThreadName-3添加用户信息:User [userId=2, name=CallService3-1, birthday=1990-03-07]
    ThreadName-1添加用户信息:User [userId=1, name=CallService3-0, birthday=1989-11-10]
    ThreadName-2添加用户信息:User [userId=3, name=CallService3-4, birthday=1995-07-26] 

    6、TransmittableThreadLocal

    必须配合如TtlRunnable/TtlCallable等一起使用,也可以配合ExecutorServiceTtlWrapper的线程池使用

    代码-上下文:

    package cn.demo.context;

    import com.alibaba.ttl.TransmittableThreadLocal;

    import cn.demo.entity.User;

    public class UserContext4 extends BaseUserContext {

    public UserContext4() {
    //3、提供的无侵入式实现
    this.context = new TransmittableThreadLocal<User>();
    }
    }

    代码-调用:

    package cn.demo.call;

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;

    import com.alibaba.ttl.TtlRunnable;

    import cn.demo.context.BaseUserContext;
    import cn.demo.context.UserContext4;
    import cn.demo.service.UserService;

    public class CallService4 extends BaseCall {

    //申明一个简单的线程池,3个核心线程
    private static final AtomicInteger threadIdCreator = new AtomicInteger(1);
    private static ExecutorService pool = Executors.newFixedThreadPool(3, (runnable) ->
    new Thread(runnable, "ThreadName-" + threadIdCreator.getAndIncrement())
    );

    public static void main(String[] args) {
    BaseUserContext userContext = new UserContext4();
    UserService userService = new UserService(userContext);
    //同时10个调用
    for (int i = 0; i < 10; i++) {
    new Thread(() -> {
    userContext.set(initUser(Thread.currentThread().getName()));
    //使用线程池进行调用
    //pool.execute(userService::addUser);
    pool.execute(TtlRunnable.get(userService::addUser));
    }, "CallService4-" + i).start();
    }
    }

    }

    控制台输出结果:(正确)

    ThreadName-2添加用户信息:User [userId=7, name=CallService4-6, birthday=1990-03-07]
    ThreadName-1添加用户信息:User [userId=4, name=CallService4-2, birthday=2000-10-01]
    ThreadName-2添加用户信息:User [userId=10, name=CallService4-9, birthday=1988-09-11]
    ThreadName-1添加用户信息:User [userId=3, name=CallService4-5, birthday=1995-07-26]
    ThreadName-2添加用户信息:User [userId=6, name=CallService4-3, birthday=1989-11-10]
    ThreadName-1添加用户信息:User [userId=1, name=CallService4-0, birthday=1989-11-10]
    ThreadName-2添加用户信息:User [userId=9, name=CallService4-8, birthday=2000-10-01]
    ThreadName-3添加用户信息:User [userId=5, name=CallService4-1, birthday=1988-09-11]
    ThreadName-1添加用户信息:User [userId=2, name=CallService4-4, birthday=1990-03-07]
    ThreadName-3添加用户信息:User [userId=8, name=CallService4-7, birthday=1995-07-26]

     项目地址:

    https://github.com/wangymd/ThreadTest.git

  • 相关阅读:
    [Codeforces Round #498 (Div. 3)] -F. Xor-Paths (折半搜索)
    Best Reward [HDU
    [Educational Codeforces Round 72] A. Creating a Character (简单数学)
    [Codeforces Round #624 (Div. 3)] -E. Construct the Binary Tree (构造二叉树)
    [Codeforces Round #626 (Div. 2, based on Moscow Open Olympiad in Informatics)] -D. Present(异或性质,按位拆分,双指针)
    [Codeforces Round #626 (Div. 2, based on Moscow Open Olympiad in Informatics)] -D. Present(异或性质,按位拆分,树桩数组)
    [Educational Codeforces Round 83 ] E. Array Shrinking
    [AtCoder Beginner Contest 158]
    [CodeCraft-20 (Div. 2)]- E. Team Building (状压DP)
    HDU 3308 LCIS (线段树区间合并)
  • 原文地址:https://www.cnblogs.com/wangymd/p/11012658.html
Copyright © 2020-2023  润新知