关于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
原文:https://www.cnblogs.com/wangymd/p/11012658.html