最初にこの研究ノートを通じてこれらの問題を解決することを望んで、一般的な質問の下にThreadLocalに記載されている古いルーチン、:
- ThreadLocalのは、すべての問題を解決するのですか?
- どのようにThreadLocalを使用するには?
- 何であるかThreadLocalの原則?
- ThreadLocalのを使用して、実際のプロジェクトのいくつかの例をできますか?
基本
ThreadLocalのスレッドローカル変数、および異なる共通の変数は以下のとおりです。各スレッドは、この変数のコピーが独立して(設定方法)と、この変数のアクセス(getメソッド)を変更することができます保持していない、とスレッド間の競合。
ThreadLocalの一般的に定義されたクラスのインスタンスであろうprivate static
状態を可能にし、ThreadLocalのインスタンスをスレッドであろう、修飾が一緒に結合している、ビジネス、通常いくつかのビジネスThreadLocalのパッケージID(ユーザーIDまたはトランザクションID)と- ID異なるスレッドによって使用されますそれは同じではありません。
使い方
CASE1
ビューの特定のポイントから、ThreadLocalのは、Javaでの並列プログラミングのための追加的なアイデアを提供 - オブジェクト自体はスレッドセーフではありませんが、マルチスレッド同期アクセスの効果を達成する必要がある場合は、同時避け、SimpleDateFormatなどを、あなたはにThreadLocalを使用することができます変数。
public class Foo
{
// SimpleDateFormat is not thread-safe, so give one to each thread
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
public String formatIt(Date date)
{
return formatter.get().format(date);
}
}
これが唯一の各スレッドのSimpleDateFormatオブジェクトに対して一度初期化する必要があることに注意してください、実際には、メンバ変数を定義するためのカスタムスレッドのSimpleDateFormatに続いて、オブジェクトが初期化されたときに、新しいスレッドが、効果は同じですが、コードはそれがより規則的に見えるようにします。
ケース2
yunosは、ディスクのデータ移行プロジェクトを冷却する前に、我々は寸法をロックし、ユーザーを追跡する必要がない場合は、各スレッドは、移行プロセスの前に、あなたは現在のユーザーのロックを取得する必要があり、各キーがユーザー情報を使用することです、だから、ThreadLocalの変数を達成するために使用することができます。
CASE3
次の例では、我々はMyRunnableオブジェクトを定義し、このオブジェクトはMyRunnableスレッド1とスレッド2を使用することであるが、内部ThreadLocal変数により、整数に各スレッドのアクセスは、独自の別のコピーです。
package org.java.learn.concurrent.threadlocal;
/**
* @author duqi
* @createTime 2018-12-29 23:25
**/
public class ThreadLocalExample {
public static class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal =
new ThreadLocal<Integer>();
@Override
public void run() {
threadLocal.set((int) (Math.random() * 100D));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(threadLocal.get());
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
thread1.join(); //wait for thread 1 to terminate
thread2.join(); //wait for thread 2 to terminate
}
}
ThreadLocalのキー知識ポイント
ソースコード解析
ThreadLocal是如何被线程使用的?原理如下图所示:Thread引用和ThreadLocal引用都在栈上,Thread引用会引用一个ThreadLocalMap对象,这个map中的key是ThreadLocal对象(使用WeakReference包装),value是业务上变量的值。
首先看java.lang.Thread
中的代码:
public
class Thread implements Runnable {
//......其他源码
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//......其他源码
Thread中的threadLocals变量指向的是一个map,这个map就是ThreadLocal.ThreadLocalMap,里面存放的是跟当前线程绑定的ThreadLocal变量;inheritableThreadLocals的作用相同,里面也是存放的ThreadLocal变量,但是存放的是从当前线程的父线程继承过来的ThreadLocal变量。
在看java.lang.ThreadLocal
类,主要的成员和接口如下:
withInitial方法,Java 8以后用于初始化ThreadLocal的一种方法,在外部调用get()方法的时候,会通过Supplier确定变量的初始值;
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); }
get方法,获取当前线程的变量副本,如果当前线程还没有创建该变量的副本,则需要通过调用
initialValue
方法来设置初始值;get方法的源代码如下,首先通过当前线程获取当前线程对应的map,如果map不为空,则从map中取出对应的Entry,然后取出对应的值;如果map为空,则调用setInitialValue设置初始值;如果map不为空,当前ThreadLocal实例对应的Entry为空,则也需要设置初始值。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(); }
set方法,跟get方法一样,先获取当前线程对应的map,如果map为空,则调用createMap创建map,否则将变量的值放入map——key为当前这个ThreadLocal对象,value为变量的值。
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
remove方法,删除当前线程绑定的这个副本
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
数字0x61c88647,这个值是HASH_INCREMENT的值,普通的hashmap是使用链表来处理冲突的,但是ThreadLocalMap是使用线性探测法来处理冲突的,HASH_INCREMENT就是每次增加的步长,根据参考资料1所说,选择这个数字是为了让冲突概率最小。
/**
* 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;
父子进程数据共享
InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,实现子线程访问父线程的threadlocal变量。InheritableThreadLocal继承了ThreadLocal,并重写了childValue、getMap、createMap三个方法。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* 创建线程的时候,如果需要继承且父线程中Thread-Local变量,则需要将父线程中的ThreadLocal变量一次拷贝过来。
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* 由于重写了getMap,所以在操作InheritableThreadLocal变量的时候,将只操作Thread类中的inheritableThreadLocals变量,与threadLocals变量没有关系
**/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* 跟getMap类似,set或getInheritableThreadLocal变量的时候,将只操作Thread类中的inheritableThreadLocals变量
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
关于childValue多说两句,拷贝是如何发生的?
首先看Thread.init方法,
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
//其他源码
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
然后看ThreadLocal.createInheritedMap方法,最终会调用到newThreadLocalMap方法,这里InheritableThreadLocal对childValue做了重写,可以看出,这里确实是将父线程关联的ThreadLocalMap中的内容依次拷贝到子线程的ThreadLocalMap中了。
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++;
}
}
}
}
ThreadLocal对象何时被回收?
ThreadLocalMap中的key是ThreadLocal对象,然后ThreadLocal对象时被WeakReference包装的,这样当没有强引用指向该ThreadLocal对象之后,或者说Map中的ThreadLocal对象被判定为弱引用可达时,就会在垃圾收集中被回收掉。看下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和线程池一起使用?
ThreadLocal对象的生命周期跟线程的生命周期一样长,那么如果将ThreadLocal对象和线程池一起使用,就可能会遇到这种情况:一个线程的ThreadLocal对象会和其他线程的ThreadLocal对象串掉,一般不建议将两者一起使用。
案例学习
ThreadLocalの使用でダボ
私は主にキャッシュを要求されたシーンで使用されているのThreadLocalでダボから例を見つける、特定のコードは次のように:
@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)
public class CacheFilter implements Filter {
private CacheFactory cacheFactory;
public void setCacheFactory(CacheFactory cacheFactory) {
this.cacheFactory = cacheFactory;
}
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.CACHE_KEY))) {
Cache cache = cacheFactory.getCache(invoker.getUrl(), invocation);
if (cache != null) {
String key = StringUtils.toArgumentString(invocation.getArguments());
Object value = cache.get(key);
if (value != null) {
if (value instanceof ValueWrapper) {
return new RpcResult(((ValueWrapper)value).get());
} else {
return new RpcResult(value);
}
}
Result result = invoker.invoke(invocation);
if (!result.hasException()) {
cache.put(key, new ValueWrapper(result.getValue()));
}
return result;
}
}
return invoker.invoke(invocation);
}
この呼び出しはThreadLocalCacheが保存されて使用されます - まず、現在のスレッドがちょうど同じ引数を呼び出すことによって開始されているかどうかを判断するためにリクエストパラメータを使用するRPCコール(呼び出し)リンクで見ることができます。具体的に次のことを達成するために、ThreadLocalCacheを参照してください。
package org.apache.dubbo.cache.support.threadlocal;
import org.apache.dubbo.cache.Cache;
import org.apache.dubbo.common.URL;
import java.util.HashMap;
import java.util.Map;
/**
* ThreadLocalCache
*/
public class ThreadLocalCache implements Cache {
//ThreadLocal里存放的是参数到结果的映射
private final ThreadLocal<Map<Object, Object>> store;
public ThreadLocalCache(URL url) {
this.store = new ThreadLocal<Map<Object, Object>>() {
@Override
protected Map<Object, Object> initialValue() {
return new HashMap<Object, Object>();
}
};
}
@Override
public void put(Object key, Object value) {
store.get().put(key, value);
}
@Override
public Object get(Object key) {
return store.get().get(key);
}
}
RocketMQ
RocketMQで、私もThreadLocalの姿を見つけ、それが送られたシーンのメッセージで使用され、MQClientAPIImplは、特定のキューを選択するために必要なステップを持つサーバーを、達成するためにメッセージを送信するRMQ責任がある、特定のキューを選択別のスレッドは、ここで使用し、独自のインデックス値責任ThreadLocalの機構を備えているとき、あなたはのThreadLocalIndexを達成するために見ることができます:
package org.apache.rocketmq.client.common;
import java.util.Random;
public class ThreadLocalIndex {
private final ThreadLocal<Integer> threadLocalIndex = new ThreadLocal<Integer>();
private final Random random = new Random();
public int getAndIncrement() {
Integer index = this.threadLocalIndex.get();
if (null == index) {
index = Math.abs(random.nextInt());
if (index < 0)
index = 0;
this.threadLocalIndex.set(index);
}
index = Math.abs(index + 1);
if (index < 0)
index = 0;
this.threadLocalIndex.set(index);
return index;
}
@Override
public String toString() {
return "ThreadLocalIndex{" +
"threadLocalIndex=" + threadLocalIndex.get() +
'}';
}
}
概要
この記事ではThreadLocalのに関するいくつかの問題を解決するために主にある:(1)特定のコンセプトは何ですか?(2)どのようなシーンの開発でJavaを使用?原則(3)にThreadLocalは似ているのですか?(4)あなたはを参照することができ、その場合には、オープンソース・プロジェクトを?私はこれらのいくつかの質問は、あなたがそれのいくつかを理解しているかどうかわからないのですか?質問があれば、交換してください。
参考資料
- なぜ0x61c88647?
- JavaのThreadLocalの
- いつ、どのように私はThreadLocal変数を使用する必要がありますか?
- 技術的な暗い部屋:ジャワのThreadLocalの理解
- メモリリークのThreadLocalで詳細な分析
- 「戦闘でのJava並行処理。」
- 詳細InheritableThreadLocal
- 詳細ThreadLocalの
- ThreadLocalの使用シナリオ
- データ構造:ハッシュテーブル
バックエンド技術、JVMのトラブルシューティングと最適化、Javaのインタビューの質問、個人の成長と自己管理、および他のトピックの数にこのフォーカス、第一線の開発者は、読者のために働くと成長の経験、あなたはここで何かを得ることを期待することができます提供。