本章主要学习重要的工具类 ThreadLocal。章节分为六大模块:
从下一个小节开始,将逐一展开这六大模块的内容。
这两种场景有明显区别:第一种场景侧重于解决工具类线程不安全问题,让每个线程有独立工具类实例;第二种场景主要解决参数传递麻烦的问题。
每个线程需要一个独享的对象,这些对象通常是工具类实例。由于工具类本身不是线程安全的,多个线程共享同一个实例会有风险,因此需要为每个线程提供独立的实例。
一个班级有30个同学,只有一本教材。如果大家都抢着看,就会发生线程安全问题。使用ThreadLocal相当于复印30份教材,每人一份,互不影响。
ThreadLocal中的"Thread"代表线程,"Local"代表本地。每个线程只能访问自己的实例副本,其他线程无法访问,不存在多线程共享问题。
package thread_local;
public class ThreadLocalNormalUsage00 {
public static String date(long second) {
// 将秒转换为毫秒
Date date = new Date(second * 1000);
// 定义日期格式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 格式化日期
return dateFormat.format(date);
}
public static void main(String[] args) {
// 线程1:打印10秒后的时间
new Thread(() -> System.out.println(date(10))).start();
// 线程2:打印1007秒后的时间
new Thread(() -> System.out.println(date(1007))).start();
}
}
package thread_local;
public class ThreadLocalNormalUsage01 {
public static String date(long second) {
Date date = new Date(second * 1000);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return dateFormat.format(date);
}
public static void main(String[] args) {
// 使用线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
final long taskSecond = i;
threadPool.submit(() -> {
System.out.println(date(taskSecond));
});
}
threadPool.shutdown();
}
}
package thread_local;
public class ThreadLocalNormalUsage02 {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String date(long second) {
Date date = new Date(second * 1000);
return DATE_FORMAT.format(date);
}
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
final long taskSecond = i;
threadPool.submit(() -> {
System.out.println(date(taskSecond));
});
}
threadPool.shutdown();
}
}
package thread_local;
public class ThreadLocalNormalUsage03 {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String date(long second) {
Date date = new Date(second * 1000);
synchronized (ThreadLocalNormalUsage03.class) {
return DATE_FORMAT.format(date);
}
}
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
final long taskSecond = i;
threadPool.submit(() -> {
System.out.println(date(taskSecond));
});
}
threadPool.shutdown();
}
}
package thread_local;
public class ThreadLocalNormalUsage04 {
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT_THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String date(long second) {
Date date = new Date(second * 1000);
SimpleDateFormat dateFormat = DATE_FORMAT_THREAD_LOCAL.get();
return dateFormat.format(date);
}
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
final long taskSecond = i;
threadPool.submit(() -> {
System.out.println(date(taskSecond));
});
}
threadPool.shutdown();
}
}
package thread_local;
public class ThreadLocalNormalUsage05 {
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static String date(long second) {
Date date = new Date(second * 1000);
SimpleDateFormat dateFormat = DATE_FORMAT_THREAD_LOCAL.get();
return dateFormat.format(date);
}
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
final long taskSecond = i;
threadPool.submit(() -> {
System.out.println(date(taskSecond));
});
}
threadPool.shutdown();
}
}
通过以上代码示例,我们逐步展示了如何使用ThreadLocal解决多线程环境下工具类实例的线程安全问题。最终方案不仅保证了线程安全,还避免了synchronized带来的性能损耗,实现了高效的内存利用。每个线程都有自己的独享对象,不同线程之间互不干扰,完美契合了ThreadLocal的设计初衷。
在实际开发中,一个请求可能需要调用多个方法,每个方法都需要使用到某些相同的对象(如用户信息)。如果每次都通过参数传递这些对象,会导致代码冗余且难以维护。
ThreadLocal 无需同步,也不需要 ConcurrentHashMap,可以在不影响性能的情况下,避免层层传递参数,直接达到共享对象的目的。
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class ServiceOne {
public void process(User user) {
UserContextHolder.setUser(user);
}
}
public class UserContextHolder {
private static final ThreadLocal<User> USER_THREAD_LOCAL = new ThreadLocal<>();
public static void setUser(User user) {
USER_THREAD_LOCAL.set(user);
}
public static User getUser() {
return USER_THREAD_LOCAL.get();
}
}
public class ServiceTwo {
public void process() {
User user = UserContextHolder.getUser();
System.out.println("ServiceTwo: " + user.getName());
}
}
public class ServiceThree {
public void process() {
User user = UserContextHolder.getUser();
System.out.println("ServiceThree: " + user.getName());
}
}
public class ThreadLocalExample {
public static void main(String[] args) {
ServiceOne serviceOne = new ServiceOne();
ServiceTwo serviceTwo = new ServiceTwo();
ServiceThree serviceThree = new ServiceThree();
User user = new User("超哥");
serviceOne.process(user);
serviceTwo.process();
serviceThree.process();
}
}
ServiceTwo: 超哥
ServiceThree: 超哥
在这个示例中,我们通过 ThreadLocal 在不同的方法之间共享了 User 对象,而无需通过参数传递。每个线程都有自己的 User 对象副本,避免了线程安全问题,同时提高了代码的可读性和维护性。这种方法特别适用于需要在多个方法中共享某些对象的场景,如用户信息、事务 ID 等。
三个重要组件 :Thread、ThreadLocal 和 ThreadLocalMap。
关系 :每个线程有一个 ThreadLocalMap,ThreadLocal 作为键,对应的值存储在该线程的 ThreadLocalMap 中。
get 方法 :
set 方法 :
remove 方法 :获取当前线程的 ThreadLocalMap,并以当前 ThreadLocal 为键,从 ThreadLocalMap 中删除对应的键值对。
initialValue 方法 :默认返回 null,可重写该方法为 ThreadLocal 设置初始值,仅在第一次调用 get 方法且未设置值时触发。
场景一:工具类线程安全问题
场景二:方法间传递公用信息