在微服务中使用线程变量(ThreadLocal)
背景在微服务开发中,经常涉及到多线程需要共享变量的时候,传统的解决方案就是就是使用互斥锁,使得在每个时刻只能有一个线程访问该变量,好处就是便于编码(直接使用 synchronized 关键字进行同步访问),但是缺点也明显,这种方法增加了线程间的竞争,降低了效率。本文将以另一种思路去解决这个问题,也就是使用ThreadLocal。ThreadLocal介绍ThreadLocal作为JD...
·
背景
- 在微服务开发中,经常涉及到多线程需要共享变量的时候,传统的解决方案就是就是使用互斥锁,使得在每个时刻只能有一个线程访问该变量,好处就是便于编码(直接使用 synchronized 关键字进行同步访问),但是缺点也明显,这种方法增加了线程间的竞争,降低了效率。
- 本文将以另一种思路去解决这个问题,也就是使用
ThreadLocal
。
ThreadLocal介绍
- ThreadLocal作为JDK1.2以来的一个java.lang包下的一个类,这个类的主要目的是提供线程本地的变量,所以也有很多地方把这个类叫做线程本地变量。
- 通常提到多线程,都会考虑变量同步的问题,但是ThreadLocal并不是为了解决多线程共享变量同步的问题,而是为了让每个线程的变量不互相影响,相当于线程之间操纵的都是变量的副本,自然就不用考虑多线程竞争的问题,也自然没有性能损耗。
ThreadLocal常用API
- threadLocal.get()方法,取当前线程存放在ThreadLocal里的数据
- threadLocal.set(T value)方法,设置当前线程在ThreadLocal里的数据
- threadLocal.remove()方法,移除当前线程在ThreadLocal里的数据
- threadLocal.initialValue(),返回当前线程在ThreadLocal里的初始值
ThreadLocal使用
- 创建entity
@Data
@ToString
public class User {
private Integer userId;
private String name;
private LocalDate birthday;
}
- 创建ContextHolder
public class UserContextHolder {
private static final ThreadLocal<User> context = new ThreadLocal<>();
/**
* 设置用户信息
* @param user -- 用户信息
*/
public static void set(User user) {
context.set(user);
}
/**
* 获取用户信息
* @return -- 用户信息
*/
public static User get() {
return context.get();
}
}
- 创建一个Service
public class UserService {
public void addUser() {
System.out.println(Thread.currentThread().getName() + "添加用户信息:" + UserContext.get());
}
}
- 测试
public class CallService {
//用户编号创建器
private static final AtomicInteger creator = new AtomicInteger(1);
//备选生日
private static final LocalDate[] birthdays = {LocalDate.of(1988, 9, 11),
LocalDate.of(1989, 11, 10),
LocalDate.of(1990, 3, 7),
LocalDate.of(1995, 7, 26),
LocalDate.of(2000, 10, 1)
};
private static final int birthdayLength = birthdays.length;
public static void main(String[] args) {
UserService userService = new UserService();
//同时10个调用
for (int i = 0; i < 10; i++) {
new Thread(() -> {
UserContext.set(initUser(Thread.currentThread().getName()));
//进行调用
userService.addUser();
}, "callService-" + i).start();
}
}
private static User initUser(String name) {
User user = new User();
user.setUserId(creator.getAndIncrement());
user.setName(name);
user.setBirthday(birthdays[user.getUserId() % birthdayLength]);
return user;
}
}
- 打印结果和预期结果相同
callService-0添加用户信息:User(userId=1, name=callService-0, birthday=1989-11-10)
callService-2添加用户信息:User(userId=3, name=callService-2, birthday=1995-07-26)
callService-3添加用户信息:User(userId=4, name=callService-3, birthday=2000-10-01)
callService-1添加用户信息:User(userId=2, name=callService-1, birthday=1990-03-07)
callService-4添加用户信息:User(userId=5, name=callService-4, birthday=1988-09-11)
callService-5添加用户信息:User(userId=6, name=callService-5, birthday=1989-11-10)
callService-9添加用户信息:User(userId=7, name=callService-9, birthday=1990-03-07)
callService-6添加用户信息:User(userId=8, name=callService-6, birthday=1995-07-26)
callService-7添加用户信息:User(userId=9, name=callService-7, birthday=2000-10-01)
callService-8添加用户信息:User(userId=10, name=callService-8, birthday=1988-09-11)
并发场景下使用线程变量
上面介绍的方法是最简单的应用场景。但是遇到复杂的场景,ThreadLocal
好像就不那么好用。比如有这么样子的一种场景,在调用过程中继续开启线程,那么在新开启的线程中就获取不到结果;又比如使用了线程池,又可能出现资源浪费的情况。
- TransmittableThreadLocal是阿里开源的,可以解决以上问题
- maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.8.1</version>
</dependency>
- 代码改造
public class UserContextHolder {
private static final TransmittableThreadLocal<User> context = new TransmittableThreadLocal<>();
/**
* 设置用户信息
* @param user -- 用户信息
*/
public static void set(User user) {
context.set(user);
}
/**
* 获取用户信息
* @return -- 用户信息
*/
public static User get() {
return context.get();
}
}
public class CallService {
//用户编号创建器
private static final AtomicInteger creator = new AtomicInteger(1);
//备选生日
private static final LocalDate[] birthdays = {LocalDate.of(1988, 9, 11),
LocalDate.of(1989, 11, 10),
LocalDate.of(1990, 3, 7),
LocalDate.of(1995, 7, 26),
LocalDate.of(2000, 10, 1)
};
private static final int birthdayLength = birthdays.length;
//申明一个简单的线程池,3个核心线程
private static final AtomicInteger threadIdCreator = new AtomicInteger(1);
private static ExecutorService pool = Executors.newFixedThreadPool(3, (runnable) ->
new Thread(runnable, "pool-" + threadIdCreator.getAndIncrement())
);
public static void main(String[] args) {
UserService userService = new UserService();
//同时10个调用
for (int i = 0; i < 10; i++) {
new Thread(() -> {
UserContext.set(initUser(Thread.currentThread().getName()));
//使用线程池进行调用
pool.execute(TtlRunnable.get(userService::addUser));
}, "callService-" + i).start();
}
}
/**
* 初始化用户信息(模拟请求)
* @param name -- 用户名
* @return -- 用户信息
*/
private static User initUser(String name) {
User user = new User();
user.setUserId(creator.getAndIncrement());
user.setName(name);
user.setBirthday(birthdays[user.getUserId() % birthdayLength]);
return user;
}
}
- 打印结果和预期一样
更多推荐
已为社区贡献4条内容
所有评论(0)