二十一、JAVA多线程笔记:线程上下文设计模式(ThreadLocal)

        上下文是贯穿整个系统或阶段生命周期的对象,其中包含了系统全局的一些信息,比如登录后的用户信息、账号信息,以及在程序每一个阶段运行时的数据。

        设计时要考虑到全局唯一性,还要考虑有些成员只能被初始化一次。比如配置信息加载。以及在多线程环境下,上下文成员的线程安全性

责任链模式

线程上下文模式

ThreadLocal详解

简介:


        ThreadLocal类顾名思义可以理解为线程本地变量。也就是说如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。

实现思路


        Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往某个ThreadLocal里塞值的时候,都会往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

 

使用场景

        1. 在进行对象跨层访问的时候,避免方法多次传递,打破层次间的约束。

        2.线程间数据隔离

        3.进行事物操作,用于存储线程事物信息。

ThreadLocal并不是解决多线程下共享资源的技术,一般情况下,每个线程的ThreadLocal存储的都是一个全新的对象(通过new关键字创建),如果多个线程的ThreadLocal存储了一个对象的引用,那么其还是会面临资源的竞争,数据不一致等并发问题

ThreadLocal方法详解与源码分析

package com.zl.step21;

import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

import static java.lang.Thread.currentThread;

public class ThreadLocalExample {
    public static void main(String[] args) {
        ThreadLocal<Integer> tlocal = new ThreadLocal<>();


        IntStream.range(0,10).forEach( i -> {
            new Thread(()->{

                try {
                    tlocal.set(i);
                    System.out.println(currentThread()+" set i  : " + tlocal.get());


                    TimeUnit.SECONDS.sleep(1);


                    System.out.println(currentThread()+" get i  : " + tlocal.get());

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }).start();



        });

    }
}

10个线程之前互相并不影响,每个线程存入ThreadLocal之间的i完全不同彼此独立。

1.initialValue()方法

initialValue方法为ThreadLocal要保存的数据类型指定了一个初始化值,在ThreadLocal中默认返回null

代码示例:

protected T initialValue() {
    return null;
}
ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){

    @Override
    protected Object initialValue(){
        return  new Object() ;
    }

};

new  Thread(()-> {
    System.out.println(threadLocal.get());
}).start();


System.out.println(threadLocal.get());

结果:

Connected to the target VM, address: '127.0.0.1:53305', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:53305', transport: 'socket'
java.lang.Object@67bfa744
java.lang.Object@7cf10a6f

Process finished with exit code 0
 

2.set(T t) 方法

set方法主要是为了ThreadLocal指定将要被存储的数据,如果重写了initialValue() 方法, 在不调用set(T t)方法的时候,数据的初始值是initialValue()方法的计算结果,示例代码:

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 根据当前线程获取ThreadLocalMap数据结构
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
/**
 * Create the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param t the current thread
 * @param firstValue value for the initial entry of the map
 */
void createMap(Thread t, T firstValue) {
    //  以当前threadLocal为key 存放的数据为value
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
**
 * Set the value associated with key.
 *
 * @param key the thread local object
 * @param value the value to be set
 */
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    // 在map的set方法中遍历整个map的entry,如果发现ThreadLocal相同,直接替换
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //ThreadLocal相同,直接替换 
        if (k == key) {
            e.value = value;
            return;
        }
        // 创建新的数据
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    // 清理操作,防止内存溢出
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

3.get() 方法

get用于返回当前线程在ThreadLocal中的数据备份,当前线程的数据存放在一个称为ThreadLocalMap的数据结构中

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    // 获取ThreadLocalMap
    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();
}
/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
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;
}
 

ThreadLocalMap

完全类似于HashMap的数据结构,仅仅用于存放线程存放在ThreadLocal中的数据备份,ThreadLocalMap的所有方法对外都不可见

ThreadLocalMap中用于存储的事Entry,他是一个WeakReference(弱引用)类型的子类。为了在垃圾会受到时候,自动回收,防止内存溢出的情况出现。

/**
 * 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;
    }
}

ThreadLocal的内存泄漏问题分析

1.WeakReference(弱引用)在JVM触发任意GC 都会导致Entry的回收。完全是由HashMap充当的。

2.在get数据时增加检查,清除已经被垃圾回收期回收的Entry

3.在set数据时增加检查,清除已经被垃圾回收期回收的Entry

ThreadLocal实现上下文

第一版:

package com.zl.step21;


public class ActionContext {
    private static final ThreadLocal<Context> context = ThreadLocal.withInitial(Context::new) ;

    public static Context get(){
        return context.get();
    }

    static class Context{
        private Configuration configuration ;
        private OtherResource otherResource ;


        public Configuration getConfiguration(){
            return configuration ;
        }

        public void setConfiguration(Configuration configuration){
          this.configuration = configuration ;
        }


        public OtherResource getOtherResource(){
            return otherResource ;
        }

        public void setOtherResource(OtherResource otherResource){
            this.otherResource = otherResource ;
        }


    }

}


class Configuration {}
class OtherResource {}

第二版:

package com.zl.step21;


public class ActionContext {
    private static final ThreadLocal<Configuration> configuration = ThreadLocal.withInitial(Configuration::new) ;

    private static final ThreadLocal<OtherResource> otherResource = ThreadLocal.withInitial(OtherResource::new) ;


    public Configuration getConfiguration(){
        return configuration.get() ;
    }

    public void setConfiguration(Configuration conf){
        configuration.set(conf) ;
    }


    public OtherResource getOtherResource(){
        return otherResource.get();
    }

    public void setOtherResource(OtherResource oResource){
        otherResource.set(oResource) ;
    }
    
}


class Configuration {}
class OtherResource {}

ThreadLocal将指定的变量和当前线程绑定,线程间彼此隔离,持有不同对象的实例,避免竞争。

ThreadLocal存在内存泄漏的问题。

猜你喜欢

转载自blog.csdn.net/zhanglong_4444/article/details/86467798