版权声明:本文由吴仙杰创作整理,转载请注明出处:https://segmentfault.com/a/1190000009236777
1. ThreadLocal
ThreadLocal
不是一个线程,而是一个线程的本地化对象。当某个变量在使用 ThreadLocal
进行维护时,ThreadLocal
为使用该变量的每个线程分配了一个独立的变量副本,每个线程可以自行操作自己对应的变量副本,而不会影响其他线程的变量副本。
2. API 方法
ThreadLocal
的 API 提供了如下的 4 个方法。
1)protected T initialValue()
返回当前线程的局部变量副本的变量初始值。
2)T get()
返回当前线程的局部变量副本的变量值,如果此变量副本不存在,则通过 initialValue()
方法创建此副本并返回初始值。
3)void set(T value)
设置当前线程的局部变量副本的变量值为指定值。
4)void remove()
删除当前线程的局部变量副本的变量值。
在实际使用中,我们一般都要重写 initialValue()
方法,设置一个特定的初始值。
2.1 示例
首先,我们来看看不考虑多线程共享数据的情况。
现在有小明、小刚、小红三人在同一家银行,分别向各自账户存入 200 元钱:
package com.wuxianjiezh.demo.threadpool;
public class MainTest {
public static void main(String[] args) {
Bank bank = new Bank();
Thread xMThread = new Thread(() -> bank.deposit(200), "小明");
Thread xGThread = new Thread(() -> bank.deposit(200), "小刚");
Thread xHThread = new Thread(() -> bank.deposit(200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
private int money = 1000;
public void deposit(int money) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前账户余额为:" + this.money);
this.money += money;
System.out.println(threadName + "--存入 " + money + " 后账户余额为:" + this.money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
小明--当前账户余额为:1000
小红--当前账户余额为:1000
小红--存入 200 后账户余额为:1400
小刚--当前账户余额为:1000
小刚--存入 200 后账户余额为:1600
小明--存入 200 后账户余额为:1200
结果是除了小明存钱和自己账户余额能对上外,小刚和小红也都只存了 200,但他们的账户余额分别多了 200 和 400?
这是因为多个线程共享了同一个实例对象的局部变量所致。
使用 ThreadLocal
保存对象的局部变量。
import java.util.function.Supplier;
public class MainTest {
public static void main(String[] args) {
Bank bank = new Bank();
Thread xMThread = new Thread(() -> bank.deposit(200), "小明");
Thread xGThread = new Thread(() -> bank.deposit(200), "小刚");
Thread xHThread = new Thread(() -> bank.deposit(200), "小红");
xMThread.start();
xGThread.start();
xHThread.start();
}
}
class Bank {
// 初始化账户余额为 100
ThreadLocal<Integer> account = ThreadLocal.withInitial(new Supplier<Integer>() {
@Override
public Integer get() {
return 1000;
}
});
public void deposit(int money) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "--当前账户余额为:" + account.get());
account.set(account.get() + money);
System.out.println(threadName + "--存入 " + money + " 后账户余额为:" + account.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
<p>运行结果:</p>
<pre class="hljs lsl"><code class="powershell">小明--当前账户余额为:<span class="hljs-number">1000</span>
小红--当前账户余额为:<span class="hljs-number">1000</span>
小红--存入 <span class="hljs-number">200</span> 后账户余额为:<span class="hljs-number">1200</span>
小刚--当前账户余额为:<span class="hljs-number">1000</span>
小刚--存入 <span class="hljs-number">200</span> 后账户余额为:<span class="hljs-number">1200</span>
小明--存入 <span class="hljs-number">200</span> 后账户余额为:<span class="hljs-number">1200</span></code></pre>
<p>可以看到,我们要的效果达到了。各线程间同时操作自己的变量,相互间没有影响。</p>
<h1 id="item-3">3. ThreadLocal 与 Thread 同步机制的比较</h1>
<ul>
<li><p>同步机制采用了<strong>以时间换空间</strong>方式,通过<strong>对象锁</strong>保证在同一个时间,对于同一个实例对象,只有一个线程访问。</p></li>
<li><p><code>ThreadLocal</code> 采用<strong>以空间换时间</strong>方式,为每一个线程都提供一份变量,各线程间同时访问互不影响。</p></li>
</ul>
</article>