【多线程与高并发之ThreadLocal】——ThreadLocal简介

目录
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

Java多线程是Java多任务执行的基础,那当一个任务会调用多个方法时,我们如何在一个线程内传递状态呢?


一、ThreadLocal是什么?

顾名思义,ThreadLocal是本地线程变量,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,线程内数据共享,线程间数据隔离。

二、ThreadLocal的使用场景?

ThreadLocal的使用场景主要是根据它的特性来的,也就是线程内数据共享,线程间数据隔离。

1.线程内数据共享

1、参数隐式传递
2、请求调用链中信息存储 - 例如(线程调用方法的方法,耗时,错误等信息)
3、……
下面是一个简单示例:
例如,小编目前正在做的一个项目,有一个计算排行榜的功能,有如下几个步骤:

1、从redis中获取用户各个业务线的基础数据信息(转入、转出、账户权益等)2、对用户的各个业务线数据进行汇总;
3、按照盈利计算公式进行盈利计算。

在这个方法中都需要传递用户这个对象,我们在不使用ThreadLocal时的写法如下,将username传入(使用线程池):

public static void main(String[] args) throws Exception {
	ExecutorService es = Executors.newFixedThreadPool(3);
	String[] users = new String[] { "Bill", "Cindy", "Alice"};
	for (String user : users) {
		es.submit(new Task(user));
	}
	es.awaitTermination(3, TimeUnit.SECONDS);
	es.shutdown();
}
class Task implements Runnable {

	final String userName;

	public Task(String userName) {
		this.userName = userName;
	}

	@Override
	public void run() {
		new Task1().process(userName);
		new Task2().process(userName);
		new Task3().process(userName);
	}
}
class Task1 {
	public void process(String userName) {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
		}
		System.out.printf("[%s] get %s basic data from redis...\n", Thread.currentThread().getName(), userName);
	}
}

class Task2 {
	public void process(String userName) {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
		}
		System.out.printf("[%s] collect %s data has done.\n", Thread.currentThread().getName(), userName);
	}
}

class Task3 {
	public void process(String userName) {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
		}
		System.out.printf("[%s] calc %s profit has done.\n", Thread.currentThread().getName(),
				userName);
	}
}

这种在一个线程中,将一个对象传递给若干方法,我们通常称之为上下文(Context),它是一种状态,可以是用户身份、任务信息等。
频繁的给每个方法添加上下文非常麻烦,而且还占用内存空间。于是,Java就提供了一个ThreadLocal机制,可以在一个线程中传递同一个对象,这个对象在线程间是共享的。
现在我们对以上的代码进行改造:
我们定义一个上下文类UserContext,这个类中,定义了一个userThreadLocal变量用来存储用户数据,一个set方法和一个get方法分别调用了ThreadLocal的set()和get(),用来设置和获取ThreadLocal中存储的变量,另外,这个类实现了AutoCloseable 类,重写了close()方法,任务执行完毕后,可以自动关闭ThreadLocal(后面会讲到,为什么要关闭ThreadLocal)。当任务在执行时,并不需要传递user对象,而是直接从ThreadLocal中获取。
代码如下:

class UserContext implements AutoCloseable {
	private static final ThreadLocal<String> userThreadLocal = new ThreadLocal<>();

	public UserContext(String name) {
		userThreadLocal.set(name);
		System.out.printf("[%s] init user %s...\n", Thread.currentThread().getName(), UserContext.getCurrentUser());
	}

	public static String getCurrentUser() {
		return userThreadLocal.get();
	}

	@Override
	public void close() {
		System.out.printf("[%s] remove user %s...\n", Thread.currentThread().getName(),
				UserContext.getCurrentUser());
		userThreadLocal.remove();
	}
}

class Task implements Runnable {

	final String userName;

	public Task(String userName) {
		this.userName = userName;
	}

	@Override
	public void run() {
		try (var ctx = new UserContext(this.userName)) {
			new Task1().process();
			new Task2().process();
			new Task3().process();
		}
	}
}

class Task1 {
	public void process() {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
		}
		System.out.printf("[%s] get %s basic data from redis...\n", Thread.currentThread().getName(), UserContext.getCurrentUser());
	}
}

class Task2 {
	public void process() {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
		}
		System.out.printf("[%s]  collect %s data has done.\n", Thread.currentThread().getName(), UserContext.getCurrentUser());
	}

以上,就是ThreadLocal线程内数据共享的简单使用场景。
再讲一个框架上的使用场景(参数隐式传递):
Spring的事务管理器通过AOP切入业务代码,在进入业务代码前,会依据相应的事务管理器提取出相应的事务对象,假如事务管理器是DataSourceTransactionManager,就会从DataSource中获取一个连接对象,通过一定的包装后将其保存在ThreadLocal中。而且Spring也将DataSource进行了包装,重写了当中的getConnection()方法,或者说该方法的返回将由Spring来控制,这样Spring就能让线程内多次获取到的Connection对象是同一个。
为什么要放在ThreadLocal里面呢?由于Spring在AOP后并不能向应用程序传递参数。应用程序的每一个业务代码是事先定义好的,Spring并不会要求在业务代码的入口参数中必须编写Connection的入口参数。此时Spring选择了ThreadLocal,通过它保证连接对象始终在线程内部,不论什么时候都能拿到,此时Spring很清楚什么时候回收这个连接,也就是很清楚什么时候从ThreadLocal中删除这个元素。

2.线程间数据隔离

为了解决多线程并发问题、数据库连接、Session 管理等而设计的,如下参考资料,作者已经写的很详细了,我就不过多赘述了。

ThreadLocal 那点事儿
ThreadLocal 那点事儿(续集)

总结

ThreadLocal到底怎么应用,还是要根据我们的具体业务场景来分析。本篇博客介绍了ThreadLocal的一些基础知识,下篇博客让我们来分析下ThreadLocal的源码,了解下ThreadLocal是如何工作的。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幸运的梦之星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值