>Java >java지도 시간 >Java 멀티스레딩에서 ThreadLocal 사용

Java 멀티스레딩에서 ThreadLocal 사용

不言
不言앞으로
2018-10-17 16:21:532530검색

이 기사의 내용은 Java 멀티스레딩에서 ThreadLocal을 사용하는 것에 대한 내용입니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

멀티 스레드 환경에서는 synchronized 메소드를 사용하여 에 액세스하는 등 스레드로부터 안전하지 않은 변수에 액세스할 때 스레드 동기화를 수행해야 합니다. >HashMap 예시. 그러나 동기식 액세스는 동시성을 감소시키고 시스템 성능에 영향을 미칩니다. 이때 각 스레드에 독립 변수를 할당하면 스레드에 안전하지 않은 변수를 비동기 방식으로 사용할 수 있습니다. synchronized方式访问HashMap实例。但是同步访问会降低并发性,影响系统性能。这时候就可以用空间换时间,如果我们给每个线程都分配一个独立的变量,就可以用非同步的方式使用非线程安全的变量,我们称这种变量为线程局部变量。

顾名思义,线程局部变量是指每个线程都有一份属于自己独立的变量副本,不会像普通局部变量一样可以被其他线程访问到。Java并没有提供语言级的线程局部变量,而是在类库里提供了线程局部变量的功能,也就是这次的主角ThreadLocal类。

ThreadLocal的使用

Java 멀티스레딩에서 ThreadLocal 사용

Java8版本的ThreadLocal有上图所示的4个public方法和一个protected的方法,第一个方法用于返回初始值,默认是null。第二个静态方法withInitial(Supplier extends S> supplier)是Java8版本新添加的,后面三个实例方法则非常的简单。

在Java8之前,使用ThreadLocal时想要设置初始值时需要继承ThreadLocal类覆盖protected T initialValue()方法才行,例如:

ThreadLocal<integer> threadLocal = new ThreadLocal<integer>() {
    @Override
    protected Integer initialValue() {
        return 0;
    }
};</integer></integer>

在Java8版本可以使用新添加的静态方法withInitial(Supplier extends S> supplier),非常方便的设置初始值,例如:

ThreadLocal<integer> threadLocal = ThreadLocal.withInitial(() -> 0);

System.out.println(threadLocal.get());
threadLocal.set(16);
System.out.println(threadLocal.get());
threadLocal.remove();
System.out.println(threadLocal.get());

// 同一个线程的输出
0
16
0
Process finished with exit code 0</integer>

ThreadLocal的原理

那么ThreadLocal是怎么实现线程局部变量的功能的呢?其实ThreadLocal的基本原理并没有十分复杂。ThreadLocal在内部定义了一个静态类ThreadLocalMap,ThreadLocalMap的键为ThreadLocal对象,ThreadLocalMap的值就是ThreadLocal存储的值,不过这个ThreadLocalMap是在Thread类里维护的。我们来看一下ThreadLocal的部分源码:

    // ThreadLocal的set方法
    public void set(T value) {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取Map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 设置值
            map.set(this, value);
        else
            // 初始化Map
            createMap(t, value);
    }
    
    // ThreadLocal的createMap方法
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    // Thread类定义的实例域
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

可以看出ThreadLocal的核心实现就是ThreadLocalMap的实现了,ThreadLocalMap内部声明了一个Entry类来存储数据:

static class Entry extends WeakReference<threadlocal>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal> k, Object v) {
        super(k);
        value = v;
    }
}</threadlocal>

ThreadLocalMap的实现与HashMap的实现有相似的地方,比如同样是使用数组存储数据和自动扩容,不同的是hash算法与hash碰撞后的处理不一样。

        // ThreadLocalMap的set方法
        private void set(ThreadLocal> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            // 计算在Entry[]中的索引,每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal> k = e.get();
                // 如果键已存在就更新值
                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();
        }
        
        private static int nextIndex(int i, int len) {
            return ((i + 1 <p>可以看到ThreadLocalMap把Entry[]数组当成一个圆环。从计算出来的索引位置开始,如果该索引已经有数据了就判断Key是否相同,相同就更新值。否则就直到找到一个空的位置把值放进去。获取值的时候也类似,从计算出来的索引位置开始一个一个检查Key是否相同,这样hash碰撞比较多的话可能性能就不是很好。</p><p><strong>ThreadLocal的应用</strong><code><br></code></p><p><code><span style="font-family: 微软雅黑, Microsoft YaHei;">ThreadLocal的应用是非常广的,比如Java工程师非常熟悉的Spring框架中就使用了ThreadLocal来把非线程安全的状态性对象封装起来,所以我们可以把绝大部分的Bean声明为singleton作用域。我们在编写多线程代码时也可以想想是用同步的方式访问非线程安全的状态性对象比较好,还是使用ThreadLocal把非线程安全的状态性对象封装起来更好。</span><br></code></p>이름에서 알 수 있듯이 스레드 지역 변수는 각 스레드가 자신만의 독립적인 변수 복사본을 가지며 일반 지역 변수처럼 다른 스레드에서 액세스할 수 없음을 의미합니다. <code>Java</code>는 언어 수준의 스레드 로컬 변수를 제공하지 않고, 대신 이번에 주인공이 되는 클래스 라이브러리인 <code>ThreadLocal</code> 클래스에서 스레드 로컬 변수의 기능을 제공합니다. . <p class="comments-box-content"><br><strong>ThreadLocal 사용</strong></p><p style="text-align: center;"><span class="img-wrap"><img src="https://img.php.cn//upload/image/282/502/938/1539764355143351.png" title="1539764355143351.png" alt="Java 멀티스레딩에서 ThreadLocal 사용">#🎜🎜#</span>#🎜🎜##🎜🎜#Java8 버전의 ThreadLocal에는 위 그림과 같이 4개의 공용 메소드와 보호된 메소드가 있습니다. 첫 번째 메소드는 초기값을 반환하는 데 사용되며 기본값은 null입니다. 두 번째 정적 메소드 withInitial(Supplier 확장 S> 공급자)이 Java8 버전에 새로 추가되었으며 다음 세 가지 인스턴스 메소드는 매우 간단합니다. #🎜🎜##🎜🎜# Java 8 이전에는 ThreadLocal을 사용하고 초기 값을 설정하려는 경우 ThreadLocal 클래스를 상속하고 보호된 TinitialValue() 메서드를 재정의해야 했습니다. 예: #🎜🎜##🎜🎜 #rrreee#🎜🎜# Java8 버전에서는 새로 추가된 정적 메서드 withInitial(Supplier 확장 S> 공급자)을 사용할 수 있습니다. 이는 초기 값을 설정하는 데 매우 편리합니다. 예: #🎜🎜#rrreee#🎜 🎜#<strong>ThreadLocal의 원리</strong># 🎜🎜##🎜🎜#그럼 ThreadLocal은 스레드 지역 변수의 기능을 어떻게 구현합니까? 사실 ThreadLocal의 기본 원리는 그리 복잡하지 않습니다. ThreadLocal은 내부적으로 ThreadLocalMap이라는 정적 클래스를 정의합니다. ThreadLocalMap의 값은 ThreadLocal에 의해 저장되는 값입니다. ThreadLocal의 소스 코드 중 일부를 살펴보겠습니다. #🎜🎜#rrreee#🎜🎜# ThreadLocal의 핵심 구현은 ThreadLocalMap의 구현인 것을 볼 수 있습니다. ThreadLocalMap은 데이터를 저장하기 위한 Entry 클래스를 내부적으로 선언합니다. #🎜 🎜#rrreee#🎜🎜 #ThreadLocalMap의 구현은 HashMap의 구현과 유사합니다. 예를 들어 배열은 데이터를 저장하고 자동으로 확장하는 데 사용되지만 차이점은 해시 알고리즘과 해시 충돌 후 처리가 다릅니다. #🎜🎜#rrreee#🎜🎜#ThreadLocalMap이 Entry[] 배열을 링으로 처리하는 것을 볼 수 있습니다. 계산된 인덱스 위치부터 시작하여 인덱스에 이미 데이터가 있는 경우 Key가 동일한지 판단하고, 동일하면 값을 업데이트합니다. 그렇지 않으면 빈 위치를 찾아 그 안에 값을 넣을 때까지 기다리십시오. 값을 가져올 때도 마찬가지이며, 계산된 인덱스 위치부터 키가 동일한지 하나씩 확인하면 된다. 해시 충돌이 많으면 성능이 별로 좋지 않을 수 있다. #🎜🎜##🎜🎜#<strong>ThreadLocal 애플리케이션</strong><code>#🎜🎜#</code>#🎜🎜##🎜🎜#<code><span   style="max-width:90%">ThreadLocal이 널리 사용됩니다. 예를 들어 Java 엔지니어에게 매우 친숙한 Spring 프레임워크는 ThreadLocal을 사용하여 스레드로부터 안전하지 않은 상태 저장 개체를 캡슐화하므로 대부분의 Bean을 캡슐화할 수 있으므로 싱글톤 범위로 선언됩니다. 멀티 스레드 코드를 작성할 때 스레드로부터 안전하지 않은 상태 저장 개체에 동기 방식으로 액세스하는 것이 더 나은지, 아니면 스레드로부터 안전하지 않은 상태 저장 개체를 캡슐화하기 위해 ThreadLocal을 사용하는 것이 더 나은지 생각해 볼 수도 있습니다. </span>#🎜🎜#</code>#🎜🎜##🎜🎜##🎜🎜##🎜🎜#</p>

위 내용은 Java 멀티스레딩에서 ThreadLocal 사용의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제