Java线程变量问题

Posted wangymd

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程变量问题相关的知识,希望对你有一定的参考价值。

关于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

以上是关于Java线程变量问题的主要内容,如果未能解决你的问题,请参考以下文章

java线程安全问题之静态变量实例变量局部变量

java线程安全问题之静态变量实例变量局部变量

java线程安全问题之静态变量实例变量局部变量

java 局部静态变量在多线程环境下是不是有线程安全问题??

java线程安全问题之静态变量实例变量局部变量

java中开启子线程后主线程中传入的变量不变