背景
  • 在微服务开发中,经常涉及到多线程需要共享变量的时候,传统的解决方案就是就是使用互斥锁,使得在每个时刻只能有一个线程访问该变量,好处就是便于编码(直接使用 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;
    }
}
  • 打印结果和预期一样
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐