从零开始写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。
- 从零开始写javaweb框架笔记21-使框架具备AOP特性-ThreadLocal简介
- 从零开始写javaweb框架笔记19-使框架具备AOP特性-AOP技术简介
- 从零开始写javaweb框架笔记18-使框架具备AOP特性-代理技术简介
- 从零开始写javaweb框架笔记22-使框架具备AOP特性-事务管理简介
- 从零开始写javaweb框架笔记20-使框架具备AOP特性-开发AOP框架
- 从零开始写javaweb框架笔记23-使框架具备AOP特性-实现事务控制特性
- [笔记]架构探险-从零开始写JavaWeb框架-2.2. 之使框架具有aop特性-干货,让框架支持事务处理
- [笔记]架构探险-从零开始写JavaWeb框架-2.1. 之使框架具有aop特性-aop框架加载与切面运行流程分析
- 从零开始写javaweb框架笔记2-搭建web项目框架
- 从零开始写javaweb框架笔记16-搭建轻量级JAVAWEB框架-加载Controller,初始化框架
- 从零开始写javaweb框架笔记16-搭建轻量级JAVAWEB框架-加载Controller,初始化框架
- 从零开始写javaweb框架笔记7-动手开发web应用
- 从零开始写javaweb框架笔记10-搭建轻量级JAVAWEB框架-确定目标
- 从零开始写javaweb框架笔记11-搭建轻量级JAVAWEB框架-搭建开发环境
- 从零开始写javaweb框架笔记13-搭建轻量级JAVAWEB框架-开发一个类加载器
- 从零开始写javaweb框架笔记14-搭建轻量级JAVAWEB框架-实现Bean容器
- 从零开始写javaweb框架笔记15-搭建轻量级JAVAWEB框架-实现依赖注入功能
- 从零开始写javaweb框架笔记17-搭建轻量级JAVAWEB框架-请求转发
- 1035. Password (20)
- 博客推荐
- View的事件分发机制
- Python的异常处理
- 1.1算法研究-动态规划
- 从零开始写javaweb框架笔记21-使框架具备AOP特性-ThreadLocal简介
- go httpClient 好坑
- Codeforces Round #424 (Div. 2) B. Keyboard Layouts(字符串处理)
- hdu1171(01背包问题)
- 动态构建顺序表,及顺序表的插入删除算法(C语言)
- solrJ的使用以及solr集群的搭建
- Linux终端am命令用法
- cakephp表关联查询
- Mybatis 示例之 SelectKey