从零开始写javaweb框架笔记21-使框架具备AOP特性-ThreadLocal简介

来源:互联网 发布:有趣的事情 知乎 编辑:程序博客网 时间:2024/06/16 11:24

一:什么是ThreadLocal

      ThreadLocal直译为“线程本地”,或者“本地线程”,但是其实它是一个容器,不是什么线程,这个容器用于存放线程的局部变量,应该叫ThreadLoadVariable(线程局部变量)才对。

      早在JDK1.2的时代,java.lang.ThreadLocal就诞生了,它是为了解决多线程并发问题而设计的,只不过设计得有些难用,所以至今没有得到广泛使用。

       一个序列号生成器的程序可能同时会有多个线程并发访问它,要保证每个线程得到的序列都是自增的,而不是相互干扰的。

        先定义一个接口:

package org.smart4j.framework.test.threadlocal;/** * Created by jack on 2017/7/18. */public interface Sequence {    int getNumber();}

      每次调用getNumber方法可获取一个序列,下次再调用时,序列号会自增。

      再做一个线程类:

package org.smart4j.framework.test.threadlocal;/** * Created by jack on 2017/7/18. */public class ClientThread extends Thread {    private Sequence sequence;    public ClientThread(Sequence sequence) {        this.sequence = sequence;    }    @Override    public void run() {        for (int i=0; i<3 ;i++){            System.out.println(Thread.currentThread().getName()+" => "+ sequence.getNumber());            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

     线程中联系输出三次线程名与其对应的序列号。

     先不用ThreadLocal,做一个实现类,代码如下:

package org.smart4j.framework.test.threadlocal;/** * Created by jack on 2017/7/18. */public class SequenceA implements Sequence{    private static int number = 0;    public int getNumber() {        number = number+1;        return number;    }    public static void main(String [] args){        Sequence sequence = new SequenceA();        ClientThread thread1 = new ClientThread(sequence);        ClientThread thread2 = new ClientThread(sequence);        ClientThread thread3 = new ClientThread(sequence);        thread1.start();        thread2.start();        thread3.start();    }}

        number的初始值为0,每次getNumber时把number+1,在返回number,在main方法中模拟了三个线程,运行结果如下:

 

D:\programing\java1.8\jdk1.8.0_131\bin\java "-javaagent:D:\mysoftware\JetBrains\IntelliJ IDEA 2017.1.2\lib\idea_rt.jar=53528:D:\mysoftware\JetBrains\IntelliJ IDEA 2017.1.2\bin" -Dfile.encoding=UTF-8 -classpath D:\programing\java1.8\jdk1.8.0_131\jre\lib\charsets.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\deploy.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\dnsns.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\jaccess.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\localedata.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\nashorn.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\sunec.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\ext\zipfs.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\javaws.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\jce.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\jfr.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\jfxswt.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\jsse.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\management-agent.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\plugin.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\resources.jar;D:\programing\java1.8\jdk1.8.0_131\jre\lib\rt.jar;F:\mystudy\learngit\smartframework\target\classes;D:\programing\mavenrepository\javax\servlet\jstl\1.2\jstl-1.2.jar;D:\programing\mavenrepository\org\slf4j\slf4j-log4j12\1.7.7\slf4j-log4j12-1.7.7.jar;D:\programing\mavenrepository\org\slf4j\slf4j-api\1.7.7\slf4j-api-1.7.7.jar;D:\programing\mavenrepository\log4j\log4j\1.2.17\log4j-1.2.17.jar;D:\programing\mavenrepository\mysql\mysql-connector-java\5.1.26\mysql-connector-java-5.1.26.jar;D:\programing\mavenrepository\com\fasterxml\jackson\core\jackson-databind\2.8.1\jackson-databind-2.8.1.jar;D:\programing\mavenrepository\com\fasterxml\jackson\core\jackson-annotations\2.8.0\jackson-annotations-2.8.0.jar;D:\programing\mavenrepository\com\fasterxml\jackson\core\jackson-core\2.8.1\jackson-core-2.8.1.jar;D:\programing\mavenrepository\org\apache\commons\commons-lang3\3.3.2\commons-lang3-3.3.2.jar;D:\programing\mavenrepository\org\apache\commons\commons-collections4\4.0\commons-collections4-4.0.jar;D:\programing\mavenrepository\commons-dbutils\commons-dbutils\1.6\commons-dbutils-1.6.jar;D:\programing\mavenrepository\org\apache\commons\commons-dbcp2\2.1\commons-dbcp2-2.1.jar;D:\programing\mavenrepository\org\apache\commons\commons-pool2\2.3\commons-pool2-2.3.jar;D:\programing\mavenrepository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\programing\mavenrepository\cglib\cglib\2.2.2\cglib-2.2.2.jar;D:\programing\mavenrepository\asm\asm\3.3.1\asm-3.3.1.jar org.smart4j.framework.test.threadlocal.SequenceAThread-0 => 1Thread-1 => 2Thread-2 => 3Thread-0 => 4Thread-1 => 6Thread-2 => 5Thread-0 => 7Thread-2 => 8Thread-1 => 9Process finished with exit code 0

      由于线程启动顺序是随机的,所以并不是0,1,2这样的顺序,这个应该比较好理解。但是为什么Thread-0,输出的不是1,2,3,Thread-1输出的不是1,2,3了?

      因为number是静态变量,属于类的变量,所有的线程都是可以访问和修改这个变量,这样就导致了线程不安全,那么如果才能做到线程安全了?看下面的另外一个实现:

package org.smart4j.framework.test.threadlocal;/** * Created by jack on 2017/7/18. */public class SequenceB implements Sequence{    private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>(){        @Override        protected Integer initialValue() {            return 0;        }    };    public int getNumber() {        numberContainer.set(numberContainer.get()+1);        return numberContainer.get();    }    public static void main(String [] args){        Sequence sequence = new SequenceB();        ClientThread thread1 = new ClientThread(sequence);        ClientThread thread2 = new ClientThread(sequence);        ClientThread thread3 = new ClientThread(sequence);        thread1.start();        thread2.start();        thread3.start();    }}


 

        通过ThreadLocal封装了一个Integer类型的numberContainer静态成员变量,并且重写方法,设置初始值为0.在看getNumber方法,首先从numberContainer中get当前的值,加1,随后set到numberContainer中,最后在numberContainer中get出当前的值并返回。我们可以把ThreadLocal看作一个容器,这里理解起来就简单了。运行结果如下:

Thread-0 => 1Thread-1 => 1Thread-2 => 1Thread-2 => 2Thread-0 => 2Thread-1 => 2Thread-1 => 3Thread-0 => 3Thread-2 => 3

       可以看出每个线程都有自己的一个变量。每个线程都是相互独立的,同样是static变量,但对于不同的线程而言,它没有被共享,而是每个线程各一份,这样也就保证了线程安全。也就是说,ThreadLocal为每个线程提供了一个独立的副本。

      搞清楚了ThreadLocal的原理之后,我们看看ThreadLocal的源码,如下:

/* * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */package java.lang;import java.lang.ref.*;import java.util.Objects;import java.util.concurrent.atomic.AtomicInteger;import java.util.function.Supplier;/** * This class provides thread-local variables.  These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable.  {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). * * <p>For example, the class below generates unique identifiers local to each * thread. * A thread's id is assigned the first time it invokes {@code ThreadId.get()} * and remains unchanged on subsequent calls. * <pre> * import java.util.concurrent.atomic.AtomicInteger; * * public class ThreadId { *     // Atomic integer containing the next thread ID to be assigned *     private static final AtomicInteger nextId = new AtomicInteger(0); * *     // Thread local variable containing each thread's ID *     private static final ThreadLocal<Integer> threadId = *         new ThreadLocal<Integer>() { *             @Override protected Integer initialValue() { *                 return nextId.getAndIncrement(); *         } *     }; * *     // Returns the current thread's unique ID, assigning it if necessary *     public static int get() { *         return threadId.get(); *     } * } * </pre> * <p>Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist). * * @author  Josh Bloch and Doug Lea * @since   1.2 */public class ThreadLocal<T> {    /**     * ThreadLocals rely on per-thread linear-probe hash maps attached     * to each thread (Thread.threadLocals and     * inheritableThreadLocals).  The ThreadLocal objects act as keys,     * searched via threadLocalHashCode.  This is a custom hash code     * (useful only within ThreadLocalMaps) that eliminates collisions     * in the common case where consecutively constructed ThreadLocals     * are used by the same threads, while remaining well-behaved in     * less common cases.     */    private final int threadLocalHashCode = nextHashCode();    /**     * The next hash code to be given out. Updated atomically. Starts at     * zero.     */    private static AtomicInteger nextHashCode =        new AtomicInteger();    /**     * The difference between successively generated hash codes - turns     * implicit sequential thread-local IDs into near-optimally spread     * multiplicative hash values for power-of-two-sized tables.     */    private static final int HASH_INCREMENT = 0x61c88647;    /**     * Returns the next hash code.     */    private static int nextHashCode() {        return nextHashCode.getAndAdd(HASH_INCREMENT);    }    /**     * Returns the current thread's "initial value" for this     * thread-local variable.  This method will be invoked the first     * time a thread accesses the variable with the {@link #get}     * method, unless the thread previously invoked the {@link #set}     * method, in which case the {@code initialValue} method will not     * be invoked for the thread.  Normally, this method is invoked at     * most once per thread, but it may be invoked again in case of     * subsequent invocations of {@link #remove} followed by {@link #get}.     *     * <p>This implementation simply returns {@code null}; if the     * programmer desires thread-local variables to have an initial     * value other than {@code null}, {@code ThreadLocal} must be     * subclassed, and this method overridden.  Typically, an     * anonymous inner class will be used.     *     * @return the initial value for this thread-local     */    protected T initialValue() {        return null;    }    /**     * Creates a thread local variable. The initial value of the variable is     * determined by invoking the {@code get} method on the {@code Supplier}.     *     * @param <S> the type of the thread local's value     * @param supplier the supplier to be used to determine the initial value     * @return a new thread local variable     * @throws NullPointerException if the specified supplier is null     * @since 1.8     */    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {        return new SuppliedThreadLocal<>(supplier);    }    /**     * Creates a thread local variable.     * @see #withInitial(java.util.function.Supplier)     */    public ThreadLocal() {    }    /**     * 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 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;    }    /**     * 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 map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }    /**     * Removes the current thread's value for this thread-local     * variable.  If this thread-local variable is subsequently     * {@linkplain #get read} by the current thread, its value will be     * reinitialized by invoking its {@link #initialValue} method,     * unless its value is {@linkplain #set set} by the current thread     * in the interim.  This may result in multiple invocations of the     * {@code initialValue} method in the current thread.     *     * @since 1.5     */     public void remove() {         ThreadLocalMap m = getMap(Thread.currentThread());         if (m != null)             m.remove(this);     }    /**     * Get the map associated with a ThreadLocal. Overridden in     * InheritableThreadLocal.     *     * @param  t the current thread     * @return the map     */    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }    /**     * 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) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }    /**     * Factory method to create map of inherited thread locals.     * Designed to be called only from Thread constructor.     *     * @param  parentMap the map associated with parent thread     * @return a map containing the parent's inheritable bindings     */    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {        return new ThreadLocalMap(parentMap);    }    /**     * Method childValue is visibly defined in subclass     * InheritableThreadLocal, but is internally defined here for the     * sake of providing createInheritedMap factory method without     * needing to subclass the map class in InheritableThreadLocal.     * This technique is preferable to the alternative of embedding     * instanceof tests in methods.     */    T childValue(T parentValue) {        throw new UnsupportedOperationException();    }    /**     * An extension of ThreadLocal that obtains its initial value from     * the specified {@code Supplier}.     */    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {        private final Supplier<? extends T> supplier;        SuppliedThreadLocal(Supplier<? extends T> supplier) {            this.supplier = Objects.requireNonNull(supplier);        }        @Override        protected T initialValue() {            return supplier.get();        }    }    /**     * 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++;                    }                }            }        }        /**         * 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;        }        /**         * 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();        }        /**         * 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;                }            }        }        /**         * Replace a stale entry encountered during a set operation         * with an entry for the specified key.  The value passed in         * the value parameter is stored in the entry, whether or not         * an entry already exists for the specified key.         *         * As a side effect, this method expunges all stale entries in the         * "run" containing the stale entry.  (A run is a sequence of entries         * between two null slots.)         *         * @param  key the key         * @param  value the value to be associated with key         * @param  staleSlot index of the first stale entry encountered while         *         searching for key.         */        private void replaceStaleEntry(ThreadLocal<?> key, Object value,                                       int staleSlot) {            Entry[] tab = table;            int len = tab.length;            Entry e;            // Back up to check for prior stale entry in current run.            // We clean out whole runs at a time to avoid continual            // incremental rehashing due to garbage collector freeing            // up refs in bunches (i.e., whenever the collector runs).            int slotToExpunge = staleSlot;            for (int i = prevIndex(staleSlot, len);                 (e = tab[i]) != null;                 i = prevIndex(i, len))                if (e.get() == null)                    slotToExpunge = i;            // Find either the key or trailing null slot of run, whichever            // occurs first            for (int i = nextIndex(staleSlot, len);                 (e = tab[i]) != null;                 i = nextIndex(i, len)) {                ThreadLocal<?> k = e.get();                // If we find key, then we need to swap it                // with the stale entry to maintain hash table order.                // The newly stale slot, or any other stale slot                // encountered above it, can then be sent to expungeStaleEntry                // to remove or rehash all of the other entries in run.                if (k == key) {                    e.value = value;                    tab[i] = tab[staleSlot];                    tab[staleSlot] = e;                    // Start expunge at preceding stale entry if it exists                    if (slotToExpunge == staleSlot)                        slotToExpunge = i;                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);                    return;                }                // If we didn't find stale entry on backward scan, the                // first stale entry seen while scanning for key is the                // first still present in the run.                if (k == null && slotToExpunge == staleSlot)                    slotToExpunge = i;            }            // If key not found, put new entry in stale slot            tab[staleSlot].value = null;            tab[staleSlot] = new Entry(key, value);            // If there are any other stale entries in run, expunge them            if (slotToExpunge != staleSlot)                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);        }        /**         * Expunge a stale entry by rehashing any possibly colliding entries         * lying between staleSlot and the next null slot.  This also expunges         * any other stale entries encountered before the trailing null.  See         * Knuth, Section 6.4         *         * @param staleSlot index of slot known to have null key         * @return the index of the next null slot after staleSlot         * (all between staleSlot and this slot will have been checked         * for expunging).         */        private int expungeStaleEntry(int staleSlot) {            Entry[] tab = table;            int len = tab.length;            // expunge entry at staleSlot            tab[staleSlot].value = null;            tab[staleSlot] = null;            size--;            // Rehash until we encounter null            Entry e;            int i;            for (i = nextIndex(staleSlot, len);                 (e = tab[i]) != null;                 i = nextIndex(i, len)) {                ThreadLocal<?> k = e.get();                if (k == null) {                    e.value = null;                    tab[i] = null;                    size--;                } else {                    int h = k.threadLocalHashCode & (len - 1);                    if (h != i) {                        tab[i] = null;                        // Unlike Knuth 6.4 Algorithm R, we must scan until                        // null because multiple entries could have been stale.                        while (tab[h] != null)                            h = nextIndex(h, len);                        tab[h] = e;                    }                }            }            return i;        }        /**         * Heuristically scan some cells looking for stale entries.         * This is invoked when either a new element is added, or         * another stale one has been expunged. It performs a         * logarithmic number of scans, as a balance between no         * scanning (fast but retains garbage) and a number of scans         * proportional to number of elements, that would find all         * garbage but would cause some insertions to take O(n) time.         *         * @param i a position known NOT to hold a stale entry. The         * scan starts at the element after i.         *         * @param n scan control: {@code log2(n)} cells are scanned,         * unless a stale entry is found, in which case         * {@code log2(table.length)-1} additional cells are scanned.         * When called from insertions, this parameter is the number         * of elements, but when from replaceStaleEntry, it is the         * table length. (Note: all this could be changed to be either         * more or less aggressive by weighting n instead of just         * using straight log n. But this version is simple, fast, and         * seems to work well.)         *         * @return true if any stale entries have been removed.         */        private boolean cleanSomeSlots(int i, int n) {            boolean removed = false;            Entry[] tab = table;            int len = tab.length;            do {                i = nextIndex(i, len);                Entry e = tab[i];                if (e != null && e.get() == null) {                    n = len;                    removed = true;                    i = expungeStaleEntry(i);                }            } while ( (n >>>= 1) != 0);            return removed;        }        /**         * Re-pack and/or re-size the table. First scan the entire         * table removing stale entries. If this doesn't sufficiently         * shrink the size of the table, double the table size.         */        private void rehash() {            expungeStaleEntries();            // Use lower threshold for doubling to avoid hysteresis            if (size >= threshold - threshold / 4)                resize();        }        /**         * Double the capacity of the table.         */        private void resize() {            Entry[] oldTab = table;            int oldLen = oldTab.length;            int newLen = oldLen * 2;            Entry[] newTab = new Entry[newLen];            int count = 0;            for (int j = 0; j < oldLen; ++j) {                Entry e = oldTab[j];                if (e != null) {                    ThreadLocal<?> k = e.get();                    if (k == null) {                        e.value = null; // Help the GC                    } else {                        int h = k.threadLocalHashCode & (newLen - 1);                        while (newTab[h] != null)                            h = nextIndex(h, newLen);                        newTab[h] = e;                        count++;                    }                }            }            setThreshold(newLen);            size = count;            table = newTab;        }        /**         * Expunge all stale entries in the table.         */        private void expungeStaleEntries() {            Entry[] tab = table;            int len = tab.length;            for (int j = 0; j < len; j++) {                Entry e = tab[j];                if (e != null && e.get() == null)                    expungeStaleEntry(j);            }        }    }}

  public void set(T value):将值放入线程局部变量中;

  public T get():从线程局部变量中获取值;

  public void remove():从线程局部变量中移除值(有助于JVM垃圾回收)

  public T initialValue():返回线程局部变量中的初始值(默认为null)

  为什么initalValue方法是protected的呢?就是为了提醒程序员,这个方法是要程序员来实现的,要给这个线程局部变量设置一个初始值。


二:自己实现ThreadLocal


     熟悉了ThreadLocal的基本原理与AP之后,其实ThreadLoca里面不就是封装了一个Map吗?现在我们自己来实现一个TreadLocal了:

package org.smart4j.framework.test.threadlocal;import java.util.Collections;import java.util.HashMap;import java.util.Map;/** * Created by jack on 2017/7/18. */public class MyThreadLocal <T>{    //创建一个现场安全的Map    private Map<Thread, T> container = Collections.synchronizedMap(new HashMap<Thread, T>());    public void set(T value){        container.put(Thread.currentThread(),value);    }    public T get() {        //获取目前的线程        Thread thread = Thread.currentThread();        T value = container.get(thread);        if (value == null && !container.containsKey(thread)) {            value = initialValue();            container.put(thread, value);        }        return value;    }    public void remove() {        container.remove(Thread.currentThread());    }    protected T initialValue() {        return null;    }}

    以上是一个山寨的ThreadLocal,其中定义了一个同步的Map,代码也比较简单。

    下面用MyThreadLocal再来实现一次:

package org.smart4j.framework.test.threadlocal;/** * Created by jack on 2017/7/18. */public class SequenceC implements Sequence{    private static MyThreadLocal<Integer> numberContainer = new MyThreadLocal<Integer>(){        @Override        protected Integer initialValue() {            return 0;        }    };    public int getNumber() {        numberContainer.set(numberContainer.get()+1);        return numberContainer.get();    }    public static void main(String [] args){        Sequence sequence = new SequenceC();        ClientThread thread1 = new ClientThread(sequence);        ClientThread thread2 = new ClientThread(sequence);        ClientThread thread3 = new ClientThread(sequence);        thread1.start();        thread2.start();        thread3.start();    }}

     输出如下:

Thread-0 => 1Thread-1 => 1Thread-2 => 1Thread-0 => 2Thread-2 => 2Thread-1 => 2Thread-1 => 3Thread-2 => 3Thread-0 => 3


   以上代码其实就是将ThreadLocal替换成了MyThreadLocal,仅此而已,运行效果和之前的一样,也是正确的。

   当在一个类中使用了static成员变量的时候,一定要多问问最近,这个static成员变量需要考虑线程安全吗?也就是说,多个线程需要独享最近的static成员变量吗?如果需要考虑,不妨使用ThreadLocal。




阅读全文
0 0
原创粉丝点击