まず、定義
ThreadLocal
それはJDK
、ビューの名前から、パッケージ提供ThreadLocal
の意味のローカルスレッドを意味します。
何1.1?
彼はGeshaある知るために、我々は見てThreadLocal
(に基づいてソースコードJDK 1.8
)は、このクラスの導入:
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).
それは大体まとめることができます。
TreadLocal
お互いを乱すことなく、私たちは私たちに、スレッド内のローカル変数を提供することができ、かつ、この変数と変数の一般的にも異なる、それは各スレッドに固有であり、他のスレッド。ThreadLocal
通常の変数の違いは:各スレッドは変数にコピーの完全に別個のインスタンスを初期化するために使用します。ThreadLocal
変数は通常されているprivate static
修正します。スレッドの終了時に、すべてのそれは使用していますThreadLocal
比較的インスタンスのコピーが回復されます。- それは単に
ThreadLocal
、すべての中で、時間のための練習スペースの一種でThread
維持するThreadLocal.ThreadLocalMap
、データの分離は、スレッドごとのデータを共有していないが、セキュリティスレッドのない自然な問題はありません。
1.2例
コードをYiyanbuge!
//创建ThreadLocal变量
private static ThreadLocal<String> localParam = new ThreadLocal<>();
@Test
public void threadLocalDemo() {
//创建2个线程,分别设置不同的值
new Thread(() -> {
localParam.set("Hello 风尘博客!");
//打印当前线程本地内存中的localParam变量的值
log.info("{}:{}", Thread.currentThread().getName(), localParam.get());
}, "T1").start();
new Thread(() -> {
log.info("{}:{}", Thread.currentThread().getName(), localParam.get());
}, "T2").start();
}
- 結果:
... T1:Hello 风尘博客!
... T2:null
印刷結果はT1
、スレッドの値が設定できないT2
変数があることを証明してThreadLocal
各スレッドでデータを共有することはありません。
1.3 ThreadLocal
のAPI
ThreadLocal
これは、4つのメソッドを定義しています。
get()
:現在のこのスレッドローカルコピーの値を返します。set(T value)
:指定された値にスレッドローカル変数の現在のコピーの値。initialValue()
:現在の初期値のこのスレッドローカルコピーを返します。remove()
:このスレッドローカルの現在の除去の値のコピー。
set()
そして、initialValue()
の違い
名前 | set() |
initialValue() |
---|---|---|
定義 | このスレッドは、新しい値を設定しています | この方法は、初期値を設定するために使用され、呼び出しがget() メソッドがそれほど遅延ロードされたときにトリガされます。しかし場合get() の前に行うset() そうではないよう呼びかけに、操作 |
違い | オブジェクトは、我々がコントロールを使用するときのタイミングでない生成された場合set() のモードを |
オブジェクトは、当社の制御使用して初期化されるときのタイミングinitialValue() モードを |
第二に、実現原理
ThreadLocal
特に重要な静的な内部クラスがありThreadLocalMap
、そのような分離のスレッドが実装の重要なメカニズムです。
- 各スレッドローカル変数はに格納されていない
ThreadLocal
インスタンスの内部が、呼び出し元のスレッドに保持threadLocals
内部変数、すなわち:ThreadLocal
特定のスレッドのメモリ空間に格納されているローカル変数のタイプ。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Thread
2つのがあり、クラスのあるThreadLocalMap
変数の型は、それぞれthreadLocals
、とinheritableThreadLocals
しながら、ThreadLocalMap
カスタマイズされたHashmap
、スレッドローカル変数を格納するために設計されています。デフォルトでは、これら二つの変数の各スレッドがありnull
、現在のスレッドにのみ最初の呼び出し、または彼らが近づいたときに作成されます。ThreadLocal
set()
get()
ThreadLocal
それはによって工具ハウジング、あるset()
メソッドvalue
を呼び出すスレッドの値threadLocals
内とそれが呼び出したスレッドを呼び出したときに、それを格納get()
する方法、そして、現在のスレッドの中にthreadLocals
使用に置かれる変数。呼び出し元のスレッドが終了していない場合、ローカル変数はいつもの呼び出し元のスレッドに保存されます
threadLocals
必要が呼び出すことにより、ローカル変数を使用するには、変数ThreadLocal
の変数をremove()
現在のスレッド方式から、threadLocals
ローカル変数の内部を削除します。
さらにThread
内部はthreadLocals
として設計されているMap
各スレッドが複数に関連付けることができるので、構造ThreadLocal
変数。
原理の概要
- それぞれが
Thread
維持ThreadLocalMap
参照。 ThreadLocalMap
ThreadLocal
使用、内部クラスEntry
のストレージのためには、- 调用
ThreadLocal
的set()
方法时,实际上就是往ThreadLocalMap
设置值,key
是ThreadLocal
对象,值是传递进来的对象; - 调用
ThreadLocal
的get()
方法时,实际上就是往ThreadLocalMap
获取值,key
是ThreadLocal
对象; ThreadLocal
本身并不存储值,它只是作为一个key
来让线程从ThreadLocalMa
p获取value
。
三、使用场景
3.1 ThreadLocal
的作用
- 保存线程上下文信息,在任意需要的地方可以获取.
由于ThreadLocal
的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。
- 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失.
3.2 场景一:独享对象
每个线程需要一个独享对象(通常是工具类,典型需要使用的类有SimpleDateFormat
和Random
)
这类场景阿里规范里面也提到了:
3.3 场景二:当前信息需要被线程内的所有方法共享
每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦。
演示(完整演示见文末Github)
User.java
@Data
public class User {
private String userName;
public User() {
}
public User(String userName) {
this.userName = userName;
}
}
UserContextHolder.java
public class UserContextHolder {
public static ThreadLocal<User> holder = new ThreadLocal<>();
}
Service1.java
public class Service1 {
public void process() {
User user = new User("Van");
//将User对象存储到 holder 中
UserContextHolder.holder.set(user);
new Service2().process();
}
}
Service2.java
public class Service2 {
public void process() {
User user = UserContextHolder.holder.get();
System.out.println("Service2拿到用户名: " + user.getUserName());
new Service3().process();
}
}
Service3.java
public class Service3 {
public void process() {
User user = UserContextHolder.holder.get();
System.out.println("Service3拿到用户名: " + user.getUserName());
}
}
- 测试方法
@Test
public void threadForParams() {
new Service1().process();
}
- 结果打印
Service2拿到用户名: Van
Service3拿到用户名: Van
3.4 使用ThreadLocal
的好处
- 达到线程安全的目的;
- 不需要加锁,执行效率高;
- 更加节省内存,节省开销;
- 免去传参的繁琐,降低代码耦合度。
四、问题
4.1 内存泄漏问题
内存泄露:某个对象不会再被使用,但是该对象的内存却无法被收回
- 正常情况
当Thread
运行结束后,ThreadLocal
中的value
会被回收,因为没有任何强引用了。
- 非正常情况
Thread
決して終わらない実行された、強力な参照は、コールチェーン以下、回復されることはありません
Thread-->ThreadLocalMap-->Entry(key为null)-->value
呼び出しチェーンなのでvalue
とThread
強い参照が存在し、それがvalue
リサイクルすることができない、発生する可能性がありますOOM
。
メモリリーク(アリ仕様)を回避する方法
呼び出しremove()
方法を、それが対応する削除させていただきますEntry
ので、最大使用、メモリリークを回避するために、オブジェクトをThreadLocal
呼び出すために、後remove()
の方法を。
4.2 ThreadLocal
NULLポインタの問題
ThreadLocalNPE.java
public class ThreadLocalNPE {
ThreadLocal<Long> longThreadLocal = new ThreadLocal<>();
public void set() {
longThreadLocal.set(Thread.currentThread().getId());
}
/**
* 当前返回值为基本类型,会报空指针异常,如果改成包装类型Long就不会出错
* @return
*/
public long get() {
return longThreadLocal.get();
}
}
- NULLポインタテスト
@Test
public void threadLocalNPE() {
ThreadLocalNPE threadLocalNPE = new ThreadLocalNPE();
//如果get方法返回值为基本类型,则会报空指针异常,如果是包装类型就不会出错
System.out.println(threadLocalNPE.get());
}
場合get()
この方法は、基本的なタイプの値を返し、パケットヌル・ポインタ例外が発生し、エラーがパッケージタイプではない場合。私たちがでなければならないので、これは、プリミティブ型とパッケージタイプのボクシングとアンボクシングとの関係からですget()
パッケージのメソッドの戻り値の型。