java 线程变量_Java线程变量问题-ThreadLocal

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

https://blog.youkuaiyun.com/w172087242/article/details/83375022#23_ThreadLocal_175

自己动手实验了一下。

1、maven设置

UTF-8

UTF-8

1.8

1.8

org.projectlombok

lombok

1.18.4

provided

com.alibaba

transmittable-thread-local

2.10.2

2、目录设置

d01a41b8dfe9f735692e77c76464c9f0.png

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 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 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();

}

}

代码-调用:

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();

}

}

代码-调用:

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();

}

}

代码-调用:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值