获取中...

-

Just a minute...

ThreadLocal的作用

ThreadLocal的作用是提供线程内的局部变量,这个变量在同一个线程的生命周期内起作用,可以减少同一个线程内多个方法之前传入公共资源的复杂性,并且隔离其他线程。下面是一个使用ThreadLocal的例子:

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
public class MyThreadLocal {

private static final ThreadLocal<Integer> myThreadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};


static class MyRunnable implements Runnable {
@Override
public void run() {

System.out.println(Thread.currentThread().getName() + "初始值为:" + myThreadLocal.get());
for (int i = 0; i < 5; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThreadLocal.set(myThreadLocal.get() + 1);
}
System.out.println(Thread.currentThread().getName() + "最后的值为:" + myThreadLocal.get());
}
}

public static void main(String[] args) {

for (int i = 0; i < 5; i++) {
new Thread(new MyRunnable()).start();
}

}
}

结果为:

1
2
3
4
5
6
7
8
9
10
Thread-1初始值为:0
Thread-0初始值为:0
Thread-2初始值为:0
Thread-3初始值为:0
Thread-4初始值为:0
Thread-1最后的值为:5
Thread-2最后的值为:5
Thread-4最后的值为:5
Thread-3最后的值为:5
Thread-0最后的值为:5

可以看到,当我们把变量保存在ThreadLocal中之后,并不会因为多线程而让实例变量被多个线程一起访问,而是每个线程访问的都是一份独立的变量,所以ThreadLocal是线程内提供的局部变量。当每个线程需要有一份独立的数据时,ThreadLocal是一个不错的实现方法,但如果需要多个线程共同访问同一份数据,又要考虑多线程并发的问题,则需要使用同步的方式,不要把ThreadLocal的作用弄错了。

ThreadLocal的实现

构造方法

ThreadLocal的构造方法就一个,而且是一个空的构造方法,内部什么都不做,很简单。

1
public ThreadLocal() {}

initialValue()方法

initialValue()方法用来设置ThreadLocal的初始值,实现如下:

1
2
3
protected T initialValue() {
return null;
}

这是一个protected类型的方法,使用者可以重写改方法,初始化默认的值。需要注意的是该方法调用的时机,该方法会在调用get()方法的时候第一次被调用,但是如果第一次调用的是set()方法,则不会调用该方法。而且该方法一般只会调用一次,除非调用了remove()方法后又调用了get()方法。具体参考set()、get()、remove()方法的实现。

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);
}

set()方法的思路大致为:

  1. 通过Thread.currentThread()方法获取到当前的线程
  2. 根据当前线程获取到一个ThreadLocalMap,getMap()的实现如下:
    1
    2
    3
    ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
    }

可以看到,获取的ThreadLocalMap是Thread的一个成员变量,getMap()只是返回这个成员变量,具体的实现逻辑在Thread中,下面看一下Thread中怎么定义ThreadLocalMap的:

1
2
3
4
5
6
7
8
9
10
11
ThreadLocal.ThreadLocalMap threadLocals = null;
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
}

在Thread中定义了一个成员变量ThreadLocalMap,这个Map是内部定义的一个类,自定义的Entry数组保存数据,Entry使用ThreadLocal作为key,保存需要的value。

  1. 如果ThreadLocalMap不是空的,则调用ThreadLocalMap的set方法,以当前ThreadLocal为key,保存value。
  2. 如果ThreadLocalMap是空的,则调用createMap()方法,创建一个ThreadLocalMap,并往里面插入value。该方法实现如下:
1
2
3
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

get()方法

get()方法用来获取与当前线程相关的ThreadLocal中的值。具体实现如下:

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();
}

get()方法的思路大致如下:

  1. 通过Thread.currentThread()方法获取到当前的线程
  2. 根据当前线程获取到一个ThreadLocalMap
  3. 如果ThreadLocalMap不为空,则进入第4步,否则进入6步
  4. 根据ThreadLocal引用为key,获取ThreadLocalMap中的entry
  5. 如果entry不为空,则获取里面的value,返回value,如果entry为空,则进入第6步
  6. 如果ThreadLocalMap为空或者Entry为空,则调用setInitialValue()方法,返回方法的返回值。setInitialValue()实现如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
    map.set(this, value);
    else
    createMap(t, value);
    return value;
    }

setInitialValue()方法首先调用initialValue()方法,这里可以看到上面说的,在调用get()方法的时候才会调用initialValue(),而且是先调用get(),如果先调用set(),由于set()的时候会创建一个ThreadLocalMap,再调用get()就不会走到调用initialValue()的步骤。在调用initialValue()初始化ThreadLocal后,获取当前线程,然后再走类似set()方法的流程,返回初始化的值。

可以看到ThreadLocal的设计就是每个Thread维护一个ThreadLocalMap变量,这个变量以ThreadLocal实例的引用为key,保存Value,实现线程的局部变量。

需要注意的是,在设计ThreadLocal的时候,可以采用如下方式:每个ThreadLocal类创建一个Map,然后用线程的ID作为Map的key,实例对象作为Map的value,这样就能达到各个线程的值隔离的效果。但是没有采用这种方式,主要的原因可能是性能问题:按这种方式的话,每一个Entry的数量是线程的数量,但是使用ThreadLocalMap,Entry的数量是和ThreadLocal的数量一致的。而且使用ThreadLocalMap,在线程销毁的时候,ThreadLocalMap也随之销毁。

相关文章
评论
分享
  • Java多线程之同步容器

    同步容器的作用Java中的容器主要为List、set、Map、Queue,这些容器都有不同的实现类,比如ArrayLIst、HashSet、HashMap、PriorityQueue等等,但是大部分都是非线程安全的,意味着在多线程访问...

    Java多线程之同步容器
  • Java多线程之Lock

    引入Lock的原因Java提供了一个关键字synchronized用来保证同步访问,那为什么还需要使用Lock?最根本的原因就是使用synchronized会造成线程的阻塞,资源同时只能被一个线程访问,程序效率低。比如,多个线程查询读...

    Java多线程之Lock
  • Java多线程之synchronized

    多线程引发的安全问题由于线程是共享进程中的所有资源的,那么每个线程都可以去操作进程中的某一个资源,当多个线程操作同一个资源的时候就可能出现线程安全问题。比如两个线程对同一个实例变量做加1的操作,然后打印该变量,如果变量的初始值为0,当...

    Java多线程之synchronized
  • Java中的线程的创建

    Java创建线程的三种方式 继承Thread 实现Runnable 实现Callable 继承Thread创建线程通过继承Thread的方式,可以很快的创建一个线程,Thread中有一个run()方法,只需要重写该方法,将需要另外开...

    Java中的线程的创建
  • 线程和进程

    什么是进程在百度百科中,进程有两种定义,一种是狭义定义:进程是正在运行的程序实例,比如微信、Tomcat、MySQL,这些就是一个个进程。另一种就是广义的定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是操作...

    线程和进程
  • Redis过期策略和内存淘汰策略

    Redis处理过期的keyRedis会将每个设置了过期时间的key保存在一个独立的字典中,以后会定期扫描这个字典来删除过期了的key。除了定期扫描删除策略之外,Redis同时采用惰性删除策略,在客户端访问key的时候,Redis会对这...

    Redis过期策略和内存淘汰策略
  • Redis持久化:AOF

    什么是AOF持久化Redis有两种持久化方式,RDB和AOF。RDB是将Redis中的数据保存一份到RDB文件中,而AOF不是保存键值对数据,而是保存服务器执行的写命令来记录数据库的状态。 AOF持久化的实现原理AOF持久化的实现可...

    Redis持久化:AOF
  • Redis持久化:RDB

    什么是RDB持久化RDB持久化是Redis的持久化方式之一,也被称为快照持久化,是将某一个时间节点的Redis内存中数据保存到磁盘上,生成一个RDB文件。之后,用户可以将这份文件进行备份,复制到其他的服务器上创建具有相同状态的Redi...

    Redis持久化:RDB
  • jps命令详解

    jps命令的作用jps命令是JDK中自带的命令,位于bin目录下,其作用是显示当前用户下系统中所有的Java进程的相关信息,比如pid。有了这个命令,可以很方便的显示Java进程,不过显示的是当前用户的所有Java进程,不能根据项目名...

    jps命令详解
Please check the parameter of comment in config.yml of hexo-theme-Annie!