java并发:线程同步机制之ThreadLocal


1.简述ThreadLocal

  ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。ThreadLocal是一个线程级别的局部变量,下面是线程局部变量(ThreadLocal variables)的关键点:

  A、当使用ThreadLocal维护变量时,若多个线程访问ThreadLocal实例,ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。

  B、从线程的角度看,目标变量就像是线程的本地变量,这也是类名中Local所要表达的意思。

 

2.细看ThreadLocal

ThreadLocal<T>类很简单,只有四个方法:

(1)void set(T value),该方法用来设置当前线程中变量的副本

(2)public T get(),该方法是用来获取ThreadLocal在当前线程中保存的变量副本

(3)public void remove(),该方法用来移除当前线程中变量的副本,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束以后,对应线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

(4)protected T initialValue(),该方法是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,ThreadLocal中的缺省实现直接返回一个null。

 

3.ThreadLocal示例

简单的使用方法如下:

package com.test;

public class ThreadMain {
    // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
        public Integer initialValue() {
            return 0;
        }
    };

    // ②获取下一个序列值
    public int getNextNum() {
        seqNum.set(seqNum.get() + 1);
        return seqNum.get();
    }

    public static void main(String[] args) {
        ThreadMain sn = new ThreadMain();
        // ③ 3个线程共享sn,各自产生序列号
        TestClient t1 = new TestClient(sn);
        TestClient t2 = new TestClient(sn);
        TestClient t3 = new TestClient(sn);
        t1.start();
        t2.start();
        t3.start();
    }

    private static class TestClient extends Thread {
        private ThreadMain sn;

        public TestClient(ThreadMain sn) {
            this.sn = sn;
        }

        public void run() {
            for (int i = 0; i < 3; i++) {
                // ④每个线程打出3个序列值
                System.out.println("thread[" + Thread.currentThread().getName()
                        + "] --> sn[" + sn.getNextNum() + "]");
            }
        }
    }
    
}

结果如下:

thread[Thread-0] --> sn[1]
thread[Thread-2] --> sn[1]
thread[Thread-1] --> sn[1]
thread[Thread-2] --> sn[2]
thread[Thread-0] --> sn[2]
thread[Thread-2] --> sn[3]
thread[Thread-1] --> sn[2]
thread[Thread-1] --> sn[3]
thread[Thread-0] --> sn[3]

 

另一个案例

package com.csu.thread;

class GlobalVarManager {
    private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){
        protected String initialValue() {
            return "hello";
        }
    };
    
    public static ThreadLocal<String> getglobalVars() {
        return globalVars;
    }
}

class ThreadRun implements Runnable {
    
    private ThreadLocal<String> t;
    private String str;
    
    ThreadRun(ThreadLocal<String> temp, String s) {
        this.t = temp;
        this.str = s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread()+"改变前:" + t.get());
        t.set(str);
        System.out.println(Thread.currentThread()+"改变后:" + t.get());
    }
}

public class ThreadLocalTry {

    public static void main(String[] args) {
        for (int i =1; i < 5; i++) {
            new Thread(new ThreadRun(GlobalVarManager.getglobalVars(), ""+i)).start();
        }
    }
}

结果如下:

Thread[Thread-0,5,main]改变前:hello
Thread[Thread-1,5,main]改变前:hello
Thread[Thread-0,5,main]改变后:1
Thread[Thread-2,5,main]改变前:hello
Thread[Thread-1,5,main]改变后:2
Thread[Thread-2,5,main]改变后:3
Thread[Thread-3,5,main]改变前:hello
Thread[Thread-3,5,main]改变后:4

 

 上述案例也可按如下方式来实现:

package com.csu.test;

class GlobalVarManager {
    private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){
        protected String initialValue() {
            return "hello";
        }
    };

    public static String getGlobalVars() {
        return globalVars.get();
    }

    public static void setGlobalVars(String str) {
        globalVars.set(str);
    }
}

class ThreadRun implements Runnable {

    private String str = null;

    public ThreadRun(String temp) {
        str = temp;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread()+"改变前:" + GlobalVarManager.getGlobalVars());
        GlobalVarManager.setGlobalVars(str);
        System.out.println(Thread.currentThread()+"改变后:" + GlobalVarManager.getGlobalVars());
    }
}

public class ThreadLocalTest {

    public static void main(String[] args) {
        for (int i = 1; i < 5; i++) {
            new Thread(new ThreadRun("" + i)).start();
        }
    }
}

结果如下:

Thread[Thread-3,5,main]改变前:hello
Thread[Thread-2,5,main]改变前:hello
Thread[Thread-1,5,main]改变前:hello
Thread[Thread-0,5,main]改变前:hello
Thread[Thread-1,5,main]改变后:2
Thread[Thread-2,5,main]改变后:3
Thread[Thread-3,5,main]改变后:4
Thread[Thread-0,5,main]改变后:1

 

4.ThreadLocal的实现机制

此部分内容暂没有深入研究,欲了解更多内容请参考https://www.cnblogs.com/dennyzhangdd/p/7978455.html

(1)get()方法源码如下:

(2)set()方法源码如下:

(3)remove()方法源码如下:

(4)上述几个函数涉及到如下两个函数

 

从前述源码可以看出,ThreadLocal的get、set、remove方法都是操作当前线程,而从Thread的源码可以看出该类有一个ThreadLocal.ThreadLocalMap类型的变量threadLocals,该变量在初次调用ThreadLocal的set()方法时通过createMap()方法初始化

 

5.ThreadLocalMap

ThreadLocalMap的部分源码如下:

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    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);
        }

        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

此处重点关注一下ThreadLocalMap中的几个成员变量及方法

(1)private Entry[] table;

table是一个Entry类型的数组,该变量在ThreadLocalMap的构造函数中初始化

Entry是ThreadLocalMap的一个内部类

(2)set()方法

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

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

 

(3)getEntry()方法

        /**
         * Get the entry associated with key.  This method
         * itself handles only the fast path: a direct hit of existing
         * key. It otherwise relays to getEntryAfterMiss.  This is
         * designed to maximize performance for direct hits, in part
         * by making this method readily inlinable.
         *
         * @param  key the thread local object
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

 

(4)remove()方法

        /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

 

6.总结

ThreadLocal一般都是声明在静态变量中,如果不断地创建ThreadLocal而没有调用其remove方法,将导致内存泄露,特别是在高并发的Web容器当中。

ThreadLocal在处理线程的局部变量时比synchronized同步机制解决线程安全问题更简单,更方便,且程序拥有更高的并发性。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM