- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Java多线程 ThreadLocal原理解析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本.
这里有几点需要注意:
Thread
内有自己的实例副本,且该副本只能由当前 Thread
使用。这是也是 ThreadLocal
命名的由来。Thread
有自己的实例副本,且其它 Thread
不可访问,那就不存在多线程间共享的问题。ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收.
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景.
首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象.
因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get() 、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get() 、set() 方法.
例如下面的 set 方法:
1
2
3
4
5
6
7
8
|
public
void
set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if
(map !=
null
)
map.set(
this
, value);
else
createMap(t, value);
}
|
get方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if
(map !=
null
) {
ThreadLocalMap.Entry e = map.getEntry(
this
);
if
(e !=
null
) {
@SuppressWarnings
(
"unchecked"
)
T result = (T)e.value;
return
result;
}
}
return
setInitialValue();
}
|
createMap方法:
1
2
3
|
void
createMap(Thread t, T firstValue) {
t.threadLocals =
new
ThreadLocalMap(
this
, firstValue);
}
|
ThreadLocalMap是个静态的内部类:
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
static
class
ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static
class
Entry
extends
WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super
(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private
static
final
int
INITIAL_CAPACITY =
16
;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private
Entry[] table;
/**
* The number of entries in the table.
*/
private
int
size =
0
;
/**
* The next size value at which to resize.
*/
private
int
threshold;
// Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private
void
setThreshold(
int
len) {
threshold = len *
2
/
3
;
}
/**
* Increment i modulo len.
*/
private
static
int
nextIndex(
int
i,
int
len) {
return
((i +
1
< len) ? i +
1
:
0
);
}
/**
* Decrement i modulo len.
*/
private
static
int
prevIndex(
int
i,
int
len) {
return
((i -
1
>=
0
) ? i -
1
: len -
1
);
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table =
new
Entry[INITIAL_CAPACITY];
int
i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY -
1
);
table[i] =
new
Entry(firstKey, firstValue);
size =
1
;
setThreshold(INITIAL_CAPACITY);
}
...
}
|
最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值.
实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉.
所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value.
ThreadLocalMap实现中已经考虑了这种情况,在调用 set() 、get() 、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get() 、set() 、remove() 方法的情况下.
如上文所述,ThreadLocal 适用于如下两种场景 。
每个线程需要有自己单独的实例 实例需要在多个方法中共享,但不希望被多线程共享 对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求.
对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅.
一个简单的用ThreadLocal来存储Session的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
static
final
ThreadLocal threadSession =
new
ThreadLocal();
public
static
Session getSession()
throws
InfrastructureException {
Session s = (Session) threadSession.get();
try
{
if
(s ==
null
) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
}
catch
(HibernateException ex) {
throw
new
InfrastructureException(ex);
}
return
s;
}
|
比如Java7中的SimpleDateFormat不是线程安全的,可以用ThreadLocal来解决这个问题:
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
DateUtil {
private
static
ThreadLocal<SimpleDateFormat> format1 =
new
ThreadLocal<SimpleDateFormat>() {
@Override
protected
SimpleDateFormat initialValue() {
return
new
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss"
);
}
};
public
static
String formatDate(Date date) {
return
format1.get().format(date);
}
}
|
这里的DateUtil.formatDate()就是线程安全的了。(Java8里的 [java.time.format.DateTimeFormatter] 。
(http://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) 是线程安全的,Joda time里的DateTimeFormat也是线程安全的).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
class
Context {
private
String name;
private
String cardId;
public
String getCardId() {
return
cardId;
}
public
void
setCardId(String cardId) {
this
.cardId = cardId;
}
public
String getName() {
return
this
.name;
}
public
void
setName(String name) {
this
.name = name;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
class
ExecutionTask
implements
Runnable {
private
QueryFromDBAction queryAction =
new
QueryFromDBAction();
private
QueryFromHttpAction httpAction =
new
QueryFromHttpAction();
@Override
public
void
run() {
final
Context context =
new
Context();
queryAction.execute(context);
System.out.println(
"The name query successful"
);
httpAction.execute(context);
System.out.println(
"The cardId query successful"
);
System.out.println(
"The Name is "
+ context.getName() +
" and CardId "
+ context.getCardId());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public
class
QueryFromDBAction {
public
void
execute(Context context) {
try
{
Thread.sleep(1000L);
String name =
"Jack "
+ Thread.currentThread().getName();
context.setName(name);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
}
public
void
execute(Context context) {
String name = context.getName();
String cardId = getCardId(name);
context.setCardId(cardId);
}
private
String getCardId(String name) {
try
{
Thread.sleep(1000L);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
return
"444555"
+ Thread.currentThread().getId();
}
}
|
1
2
3
4
5
6
7
8
9
10
|
public
class
ContextTest {
public
static
void
main(String[] args) {
IntStream.range(
1
,
5
)
.forEach(i ->
new
Thread(
new
ExecutionTask()).start()
);
}
}
|
The name query successful The name query successful The name query successful The name query successful The cardId query successful The Name is Jack Thread-0 and CardId 44455511 The cardId query successful The Name is Jack Thread-1 and CardId 44455512 The cardId query successful The Name is Jack Thread-2 and CardId 44455513 The cardId query successful The Name is Jack Thread-3 and CardId 44455514 。
问题:需要在每个调用Context的方法中传入进去 。
1
2
|
public
void
execute(Context context) {
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
final
class
ActionContext {
private
static
final
ThreadLocal<Context> threadLocal =
new
ThreadLocal() {
@Override
protected
Object initialValue() {
return
new
Context();
}
};
public
static
ActionContext getActionContext() {
return
ContextHolder.actionContext;
}
public
Context getContext() {
return
threadLocal.get();
}
private
static
class
ContextHolder {
private
final
static
ActionContext actionContext =
new
ActionContext();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
class
Context {
private
String name;
private
String cardId;
public
String getCardId() {
return
cardId;
}
public
void
setCardId(String cardId) {
this
.cardId = cardId;
}
public
String getName() {
return
this
.name;
}
public
void
setName(String name) {
this
.name = name;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
class
ExecutionTask
implements
Runnable {
private
QueryFromDBAction queryAction =
new
QueryFromDBAction();
private
QueryFromHttpAction httpAction =
new
QueryFromHttpAction();
@Override
public
void
run() {
queryAction.execute();
System.out.println(
"The name query successful"
);
httpAction.execute();
System.out.println(
"The cardId query successful"
);
final
Context context = ActionContext.getActionContext().getContext();
System.out.println(
"The Name is "
+ context.getName() +
" and CardId "
+ context.getCardId());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
QueryFromDBAction {
public
void
execute() {
try
{
Thread.sleep(1000L);
String name =
"Jack "
+ Thread.currentThread().getName();
ActionContext.getActionContext().getContext().setName(name);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
}
|
public class QueryFromHttpAction { 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public void execute() {
Context context = ActionContext.getActionContext().getContext();
String name = context.getName();
String cardId = getCardId(name);
context.setCardId(cardId);
}
private String getCardId(String name) {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "444555" + Thread.currentThread().getId();
}
}
|
1
2
3
4
5
6
7
8
9
10
|
public
class
ContextTest {
public
static
void
main(String[] args) {
IntStream.range(
1
,
5
)
.forEach(i ->
new
Thread(
new
ExecutionTask()).start()
);
}
}
|
The name query successful The name query successful The name query successful The name query successful The cardId query successful The Name is Jack Thread-3 and CardId 44455514 The cardId query successful The cardId query successful The Name is Jack Thread-0 and CardId 44455511 The cardId query successful The Name is Jack Thread-2 and CardId 44455513 The Name is Jack Thread-1 and CardId 44455512 。
这样写 执行过程中不会看到context的定义和声明 。
注意:在使用之前记得将上个线程中context旧值清除调,否则会重复调用(比如线程池操作) 。
线程复用会产生脏数据。由于结程池会重用Thread对象,那么与Thread绑定的类的静态属性ThreadLocal变量也会被重用。如果在实现的线程run()方法体中不显式地调用remove() 清理与线程相关的ThreadLocal信息,那么倘若下一个结程不调用set() 设置初始值,就可能get() 到重用的线程信息,包括 ThreadLocal所关联的线程对象的value值.
通常我们会使用使用static关键字来修饰ThreadLocal(这也是在源码注释中所推荐的)。在此场景下,其生命周期就不会随着线程结束而结束,寄希望于ThreadLocal对象失去引用后,触发弱引用机制来回收Entry的Value就不现实了。如果不进行remove() 操作,那么这个线程执行完成后,通过ThreadLocal对象持有的对象是不会被释放的.
以上两个问题的解决办法很简单,就是在每次用完ThreadLocal时, 必须要及时调用 remove()方法清理.
很多场景下通过ThreadLocal来透传全局上下文,会发现子线程的value和主线程不一致。比如用ThreadLocal来存储监控系统的某个标记位,暂且命名为traceId。某次请求下所有的traceld都是一致的,以获得可以统一解析的日志文件。但在实际开发过程中,发现子线程里的traceld为null,跟主线程的并不一致。这就需要使用InheritableThreadLocal来解决父子线程之间共享线程变量的问题,使整个连接过程中的traceId一致.
到此这篇关于Java多线程 ThreadLocal原理解析的文章就介绍到这了,更多相关Java多线程 ThreadLocal内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://juejin.cn/post/7022879808967639070 。
最后此篇关于Java多线程 ThreadLocal原理解析的文章就讲到这里了,如果你想了解更多关于Java多线程 ThreadLocal原理解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我将 Bootstrap 与 css 和 java 脚本结合使用。在不影响前端代码的情况下,我真的很难在css中绘制这个背景。在许多问题中,人们将宽度和高度设置为 0%。但是由于我的导航栏,我不能使用
我正在用 c 编写一个程序来读取文件的内容。代码如下: #include void main() { char line[90]; while(scanf("%79[^\
我想使用 javascript 获取矩阵数组的所有对 Angular 线。假设输入输出如下: input = [ [1,2,3], [4,5,6], [7,8,9], ] output =
可以用pdfmake绘制lines,circles和other shapes吗?如果是,是否有documentation或样本?我想用jsPDF替换pdfmake。 最佳答案 是的,有可能。 pdfm
我有一个小svg小部件,其目的是显示角度列表(参见图片)。 现在,角度是线元素,仅具有笔触,没有填充。但是现在我想使用一种“内部填充”颜色和一种“笔触/边框”颜色。我猜想line元素不能解决这个问题,
我正在为带有三角对象的 3D 场景编写一个非常基本的光线转换器,一切都工作正常,直到我决定尝试从场景原点 (0/0/0) 以外的点转换光线。 但是,当我将光线原点更改为 (0/1/0) 时,相交测试突
这个问题已经有答案了: Why do people write "#!/usr/bin/env python" on the first line of a Python script? (22 个回
如何使用大约 50 个星号 * 并使用 for 循环绘制一条水平线?当我尝试这样做时,结果是垂直(而不是水平)列出 50 个星号。 public void drawAstline() { f
这是一个让球以对角线方式下降的 UI,但球保持静止;线程似乎无法正常工作。你能告诉我如何让球移动吗? 请下载一个球并更改目录,以便程序可以找到您的球的分配位置。没有必要下载足球场,但如果您愿意,也可以
我在我的一个项目中使用 Jmeter 和 Ant,当我们生成报告时,它会在报告中显示 URL、#Samples、失败、成功率、平均时间、最短时间、最长时间。 我也想在报告中包含 90% 的时间线。 现
我有一个不寻常的问题,希望有人能帮助我。我想用 Canvas (android) 画一条 Swing 或波浪线,但我不知道该怎么做。它将成为蝌蚪的尾部,所以理想情况下我希望它的形状更像三角形,一端更大
这个问题已经有答案了: Checking Collision of Shapes with JavaFX (1 个回答) 已关闭 8 年前。 我正在使用 JavaFx 8 库。 我的任务很简单:我想检
如何按编号的百分比拆分文件。行数? 假设我想将我的文件分成 3 个部分(60%/20%/20% 部分),我可以手动执行此操作,-_-: $ wc -l brown.txt 57339 brown.tx
我正在努力实现这样的目标: 但这就是我设法做到的。 你能帮我实现预期的结果吗? 更新: 如果我删除 bootstrap.css 依赖项,问题就会消失。我怎样才能让它与 Bootstrap 一起工作?
我目前正在构建一个网站,但遇到了 transform: scale 的问题。我有一个按钮,当用户将鼠标悬停在它上面时,会发生两件事: 背景以对 Angular 线“扫过” 按钮标签颜色改变 按钮稍微变
我需要使用直线和仿射变换绘制大量数据点的图形(缩放图形以适合 View )。 目前,我正在使用 NSBezierPath,但我认为它效率很低(因为点在绘制之前被复制到贝塞尔路径)。通过将我的数据切割成
我正在使用基于 SVM 分类的 HOG 特征检测器。我可以成功提取车牌,但提取的车牌除了车牌号外还有一些不必要的像素/线。我的图像处理流程如下: 在灰度图像上应用 HOG 检测器 裁剪检测到的区域 调
我有以下图片: 我想填充它的轮廓(即我想在这张图片中填充线条)。 我尝试了形态学闭合,但使用大小为 3x3 的矩形内核和 10 迭代并没有填满整个边界。我还尝试了一个 21x21 内核和 1 迭代,但
我必须找到一种算法,可以找到两组数组之间的交集总数,而其中一个数组已排序。 举个例子,我们有这两个数组,我们向相应的数字画直线。 这两个数组为我们提供了总共 7 个交集。 有什么样的算法可以帮助我解决
简单地说 - 我想使用透视投影从近裁剪平面绘制一条射线/线到远裁剪平面。我有我认为是使用各种 OpenGL/图形编程指南中描述的方法通过单击鼠标生成的正确标准化的世界坐标。 我遇到的问题是我的光线似乎
我是一名优秀的程序员,十分优秀!