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

原文地址:https://www.cnblogs.com/wangymd/p/11012658.html

时间: 2024-10-13 04:42:26

Java线程变量问题的相关文章

Java线程工作内存与主内存变量交换过程及volatile关键字理解

Java线程工作内存与主内存变量交换过程及volatile关键字理解 1. Java内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行.此处的所谓内存模型要区别于通常所说的虚拟机堆模型: 2. 线程独有的工作内存和进程内存(主内存)之间通过8中原子操作来实现,如下图所示: 原子操作的规则(部分): 1) read,load必须连续执行,但是不保证原子性. 2) store,write必须连续执行,但是不保证原子性. 3) 不能丢失变量最后一次ass

Java线程:线程栈模型与线程的变量

Java线程:线程栈模型与线程的变量 要理解线程调度的原理,以及线程执行过程,必须理解线程栈模型. 线程栈是指某时刻时内存中线程调度的栈信息,当前调用的方法总是位于栈顶.线程栈的内容是随着程序的运行动态变化的,因此研究线程栈必须选择一个运行的时刻(实际上指代码运行到什么地方). 下面通过一个示例性的代码说明线程(调用)栈的变化过程. 这幅图描述在代码执行到两个不同时刻1.2时候,虚拟机线程调用栈示意图. 当程序执行到t.start();时候,程序多出一个分支(增加了一个调用栈B),这样,栈A.栈

Java并发学习之九——使用本地线程变量

本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.如果创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味着,如果你在一个线程里改变一个属性,全部的线程都会受到这个改变的影响.如果希望程序里的哥哥线程的属性不会被共享,Java并发API提供了一个很清楚的机制叫本地线程变量. 2.Java并发API包括Inheritable ThreadLocal类提供线程创建线程的值的遗传性.如果线程A有一个本地线程变量,然

【java】ThreadLocal线程变量的实现原理和使用场景

一.ThreadLocal线程变量的实现原理 1.ThreadLocal核心方法有这个几个 get().set(value).remove() 2.实现原理 ThreadLocal在每个线程都会创建一个线程内对应的T的副本,本T数据可以在本线程内任何地方可以被使用.线程之间互相不影响,所以是线程安全的. 3.底层结构 ThreadLocal实现各个线程数据副本的存取,是通过操作它的内部类ThreadLocalMap,进行<k,v>键值对的存取和移除. 4.set(value)方法的底层 pub

java 线程详解

5月7号  周末看了一下线程方面的内容 ,边看视频边看书还附带着参考了很多人的博客,一天的收获,写下来整理一下:感觉收获还是挺多的:过段时间可能看完java  这几大块要去看一下关于spring boot  的内容顺便  也整理一下:附上我参考的 几本书: 关于java  线程,首先要了解一下线程和进程之间的关系.区别以及他们之间的概念: 首先是线程: 什么是线程? 线程是在程序执行过程中能够执行部分代码的一个执行单元,也看看做是一个轻量级的进程:线程是程序内的程序控制流只能使用程序内分配给程序

Ubuntu 下如何设置 Java 环境变量

1.下载 jdk,当前的版本是 jdk-8u45.选择适合自己系统的版本进行下载,下载好之后放在 Download 目录下 2.然后使用 Ctrl + Alt + t 打开终端进入 Download 目录下,使用 tar -xf jdk-8u45-linux-x64.tar.gz 命令进行解压,接着再使用命令 sudo cp -r jdk1.8.0_45/ /usr/lib/,输入密码,即可将其拷贝到 /usr/lib 目录下.此时使用 ls /usr/lib/jdk1.8.0_45 命令便可查

Java 线程第三版 第四章 Thread Notification 读书笔记

一.等待与通知 public final void wait() throws InterruptedException 等待条件的发生. public final void wait(long timeout) throws InterruptedException 等待条件的发生.如果通知没有在timeout指定的时间内发生,它还是会返回. public final void wait(long timeout, int nanos) throws InterruptedException

Java线程使用大全

1.线程实现 1.Thread类 构造方法: 案例代码: public class Ex10_1_CaseThread extends Thread {// 创建一个类继承(extend)Thread类 String studentName; public Ex10_1_CaseThread(String studentName) {// 定义类的构造函数,传递参数 System.out.println(studentName + "申请访问服务器"); this.studentNam

java线程详细介绍

目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么要用join方法 七常见线程名词解释 八线程同步 九线程数据传递 本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1