質問:共有リソースへのマルチスレッドアクセスを解決するためにThreadLocalが使用されていますか?
これはよくあるインタビューの質問です。ThreadLocalについて尋ねられた場合は、その機能や注意点などを紹介した後で尋ねることができます。ThreadLocalは、共有リソースへのマルチスレッドアクセスを解決するために使用されますか?そのような問題に遭遇した場合、あなたの考えは明確でなければなりません。ここで私は参照の答えを与えます。
面接時に尋ねられたときの答え方
この質問への答えは明らかです-いいえ、共有リソースの問題を解決するためにThreadLocalは使用されません。ThreadLocalは、マルチスレッドの場合のスレッドセーフの問題を解決するために実際に使用できますが、そのリソースは共有されませんが、各スレッドに排他的です。したがって、この質問には実際には特定のトラップ要素があります。
ThreadLocalがスレッドセーフの問題を解決すると、「ロック」を使用する場合と比較して、考え方が変わり、リソースが各スレッド専用のリソースに変わり、同期操作が非常に巧妙に回避されます。具体的には、initialValueに独自のスレッド専用のリソースを作成でき、複数のスレッド間でアクセスするオブジェクトは共有されないため、当然のことながら並行性の問題は発生しません。これは、並行性の問題を解決するためのThreadLocalの主なアイデアです。
ThreadLocalに配置されたリソースを静的で装飾し、それを共有リソースにすると、ThreadLocalが使用されていても、スレッドセーフの問題も発生します。たとえば、前の例は変更されています。SimpleDateFormatの前に静的キーワードを追加して変更し、この静的オブジェクトをThreadLocalに格納すると、コードは次のようになります。
public class ThreadLocalStatic {
public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
static SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalStatic().date(finalI);
System.out.println(date);
}
});
}
threadPool.shutdown();
}
public String date(int seconds) {
Date date = new Date(1000 * seconds);
SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
return dateFormat.format(date);
}
}
class ThreadSafeFormatter {
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return ThreadLocalStatic.dateFormat;
}
}
}
次に、このリソースを複数のスレッドで取得して同時に使用すると、時間の重複の問題も発生します。実行結果は次のとおりです。
00:15
00:15
00:05
00:16
...
00:15が複数回印刷され、スレッドセーフの問題が発生したことがわかります。つまり、ThreadLocalに配置する必要のあるオブジェクトが静的によって共有および変更されている場合、現時点ではThreadLocalはまったく必要ありません。つまり、ThreadLocalを使用してもスレッドセーフの問題は解決されません。
逆に、この共有変数の場合、スレッドセーフを確保する場合は、他のメソッドを使用する必要があります。たとえば、ThreadLocalを使用する代わりに、同期またはロックなどのメソッドを使用してスレッドセーフの問題を解決できます。 ThreadLocalを使用する必要があるシナリオではありません。
ここでのこの質問への答えは、次の質問につながる可能性があります。
ThreadLocalと同期の関係は何ですか
インタビュアーは次のように質問するかもしれません:ThreadLocalと同期の両方がスレッドセーフの問題を解決できるとあなたは言ったので、ThreadLocalと同期の関係は何ですか?
最初に最初の状況についてお話ししましょう。ThreadLocalを使用してスレッドセーフの問題を解決する場合、つまり、スレッドごとにオブジェクトが生成されて排他コピーが生成されます。このシナリオでは、ThreadLocalと同期は、スレッドセーフ手段を確保するために使用されると理解できます。たとえば、前のSimpleDateFormatの例では、目標を達成するために同期を使用しただけでなく、実装スキームとしてThreadLocalも使用しました。しかし、効果と実現の原則は異なります。
- ThreadLocalは、各スレッドが独自のコピーを利用できるようにすることで、リソースの競合を回避します。
- Synchronizedは、主に重要なリソースの割り当てに使用されます。同時に、最大で1つのスレッドのみがリソースにアクセスできます。
ThreadLocalと比較すると、同期は効率が低くなりますが、メモリのコストも低くなります。このシナリオでは、ThreadLocalとsynchronizedの効果は異なりますが、どちらもスレッドセーフの目標を達成できます。
ただし、ThreadLocalの場合、使用シナリオは異なります。たとえば、ThreadLocalを使用して、複数のクラスがスレッドごとにこの情報を個別に保存するシナリオに簡単に到達できるようにする場合(たとえば、各スレッドはユーザーオブジェクトであるユーザー情報に対応します)、このシナリオでは次に、ThreadLocalはパラメーターの受け渡しを回避することに重点を置いているため、現時点では、ThreadLocalとsynchronizedは異なる次元の2つのツールです。
共有リソースへのマルチスレッドアクセスの問題を解決するためにThreadLocalを使用するかどうかについて話すと、答えは「いいえ」です。ThreadLocalの場合、各スレッドのリソースは共有されないためです。次に、ThreadLocalと同期の関係を共有しました。