欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ThreadLocal 解决多线程变量共享问题

程序员文章站 2022-05-02 13:12:25
...

版权声明:本文由吴仙杰创作整理,转载请注明出处: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 保存对象的局部变量。

``` package com.wuxianjiezh.demo.threadpool;

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>
相关标签: java 基础