ThreadLocal类实战

一 点睛

ThreadLocal 类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过 get 和 set 方法访问)时能保证各个线程的变量相对独立于其他的变量。ThreadLocal 实例通常来说都是private static 类型,用于关联线程和线程上下文。

我们可以得知 ThreadLocal 的作用:提供线程内的局部变量,不同线程之间不会相互干扰,这种变量在线程生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。

总结:

  • 线程并发:在多线程并发场景下。

  • 传递数据:可以通过 ThreadLocal 在同一线程,不同组件中传递公共变量。

  • 线程隔离:每个线程变量独立,不会互相影响。

ThreadLocal,是Thread Local Variable(线程局部变量)的意思,也许将它命名为ThreadLocalVar更加合适。

线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。

ThreadLocal类的用法非常简单,它只提供了如下三个public方法:

  • ThreadLocal():创建 ThreadLocal 对象。

  • T get():返回此线程局部变量中当前线程副本中的值。

  • void remove():删除此线程局部变量中当前线程的值。

  • void set(T value):设置此线程局部变量中当前线程副本中的值。

二 实战

1 代码

class Account
{
     /* 定义一个ThreadLocal类型的变量,该变量将是一个线程局部变量
     每个线程都会保留该变量的一个副本 */
     private ThreadLocal<String> name = new ThreadLocal<>();
     // 定义一个初始化name成员变量的构造器
     public Account(String str)
     {
           this.name.set(str);
           // 下面代码用于访问当前线程的name副本的值
           System.out.println("---" + this.name.get());
     }
     // name的setter和getter方法
     public String getName()
     {
           return name.get();
     }
     public void setName(String str)
     {
           this.name.set(str);
     }
}
class MyTest extends Thread
{
     // 定义一个Account类型的成员变量
     private Account account;
     public MyTest(Account account, String name)
     {
           super(name);
           this.account = account;
     }
     public void run()
     {
           // 循环10次
           for (int i = 0 ; i < 10 ; i++)
           {
                // 当i == 6时输出将账户名替换成当前线程名
                if (i == 6)
                {
                     account.setName(getName());
                }
                // 输出同一个账户的账户名和循环变量
                System.out.println(account.getName()
                     + " 账户的i值:" + i);
           }
     }
}
public class ThreadLocalTest
{
     public static void main(String[] args)
     {
           // 启动两条线程,两条线程共享同一个Account,
           // 主线程中有一个Account的name,线程甲和线程乙中也各有一个Account的name,三者互不干扰
           Account at = new Account("初始名");
           /*
           虽然两条线程共享同一个账户,即只有一个账户名
           但由于账户名是ThreadLocal类型的,所以每条线程
           都完全拥有各自的账户名副本,所以从i == 6之后,将看到两条
           线程访问同一个账户时看到不同的账户名。
           */
           new MyTest(at , "线程甲").start();
           new MyTest(at , "线程乙").start ();
     }
}

2 运行

---初始名
null 账户的i值:0
null 账户的i值:0
null 账户的i值:1
null 账户的i值:1
null 账户的i值:2
null 账户的i值:2
null 账户的i值:3
null 账户的i值:3
null 账户的i值:4
null 账户的i值:4
null 账户的i值:5
线程甲 账户的i值:6
线程甲 账户的i值:7
线程甲 账户的i值:8
线程甲 账户的i值:9
null 账户的i值:5
线程乙 账户的i值:6
线程乙 账户的i值:7
线程乙 账户的i值:8
线程乙 账户的i值:9

3 说明

账号名实际有3个副本,主线程一个,另外启动的两个线程各一个,它们的值互不干扰,每个线程完全拥有自己的ThreadLocal变量,这就是ThreadLocal的用途。

三 对比

1 不用 ThreadLocal

a 代码

package threadLocal;

/**
* @className: ThreadLocalDemoWithoutThreadLocal
* @description: 线程隔离
* 在多线程并发的场景下,每个线程中的变量都是相互独立
* 线程A:设置(变量1) 获取(变量1)
* 线程B:设置(变量2) 获取(变量2)
* @date: 2021/12/22
* @author: cakin
*/
public class ThreadLocalDemoWithoutThreadLocal {
    // 变量
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        ThreadLocalDemoWithoutThreadLocal demo = new ThreadLocalDemoWithoutThreadLocal();


        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                // 每个线程:存一个变量,过一会,取出这个变量
                demo.setContent(Thread.currentThread().getName() + "的数据");
                System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
            }).start();
        }
    }
}

b 测试结果

Thread-0--->Thread-1的数据
Thread-2--->Thread-1的数据
Thread-4--->Thread-4的数据
Thread-3--->Thread-3的数据
Thread-1--->Thread-1的数据
Thread-5--->Thread-5的数据
Thread-6--->Thread-6的数据
Thread-7--->Thread-7的数据
Thread-9--->Thread-9的数据
Thread-8--->Thread-8的数据

2 使用 ThreadLocal

a 代码

package threadLocal;

/**
* @className: ThreadLocalDemoWithThreadLocal
* @description: 线程隔离
* 在多线程并发的场景下,每个线程中的变量都是相互独立
* 线程A:设置(变量1) 获取(变量1)
* 线程B:设置(变量2) 获取(变量2)
* ThreadLocal:
* set():将变量绑定到当前线程中
* get():获取当前线程绑定的变量
* @date: 2021/12/22
* @author: cakin
*/
public class ThreadLocalDemoWithThreadLocal {
    ThreadLocal<String> t1 = new ThreadLocal<>();
    // 变量
    private String content;

    public String getContent() {
        return t1.get();
    }

    public void setContent(String content) {
        // 变量 content 绑定到当前线程
        t1.set(content);
    }

    public static void main(String[] args) {
        ThreadLocalDemoWithThreadLocal demo = new ThreadLocalDemoWithThreadLocal();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                // 每个线程:存一个变量,过一会,取出这个变量
                demo.setContent(Thread.currentThread().getName() + "的数据");
                System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
            }).start();
        }
    }
}

b 测试

Thread-1--->Thread-1的数据
Thread-2--->Thread-2的数据
Thread-0--->Thread-0的数据
Thread-3--->Thread-3的数据
Thread-4--->Thread-4的数据
Thread-6--->Thread-6的数据
Thread-5--->Thread-5的数据
Thread-8--->Thread-8的数据
Thread-7--->Thread-7的数据
Thread-9--->Thread-9的数据

四 复杂对象的使用

1 说明

我们在实际开发时,常常会用到列表对象,这里举一个例子。

2 代码

package threadLocal;


import java.util.ArrayList;
import java.util.Date;
import java.util.List;


/**
* @className: ThreadLocalDemoAboutListAndObj
* @description: 线程隔离
* 在多线程并发的场景下,每个线程中的变量都是相互独立
* 线程A:设置(变量1) 获取(变量1)
* 线程B:设置(变量2) 获取(变量2)
* ThreadLocal:
* set():将变量绑定到当前线程中
* get():获取当前线程绑定的变量
* @date: 2021/12/22
* @author: cakin
*/
public class ThreadLocalDemoAboutListAndObj {
    ThreadLocal<List<Date>> t1 = new ThreadLocal<>();

    // 比较复杂的对象
    private static List<Date> list = new ArrayList<>(1);

    public List<Date> getList() {
        return t1.get();
    }

    public void setList(List<Date> list) {
        t1.set(list);
    }

    public static void main(String[] args) {
        ThreadLocalDemoAboutListAndObj demo = new ThreadLocalDemoAboutListAndObj();
        for (int i = 0; i < 10; i++) {
            Date date = new Date(2021, 12, i);
            List<Date> objects = new ArrayList<>();
            objects.add(date);
            new Thread(() -> {
                demo.setList(objects);
                System.out.println(Thread.currentThread().getName() + "--->" + demo.getList());
            }).start();
        }
    }
}

3 测试

Thread-3--->[Tue Jan 03 00:00:00 CST 3922]
Thread-9--->[Mon Jan 09 00:00:00 CST 3922]
Thread-2--->[Mon Jan 02 00:00:00 CST 3922]
Thread-4--->[Wed Jan 04 00:00:00 CST 3922]
Thread-1--->[Sun Jan 01 00:00:00 CST 3922]
Thread-7--->[Sat Jan 07 00:00:00 CST 3922]
Thread-8--->[Sun Jan 08 00:00:00 CST 3922]
Thread-5--->[Thu Jan 05 00:00:00 CST 3922]
Thread-0--->[Sat Dec 31 00:00:00 CST 3921]
Thread-6--->[Fri Jan 06 00:00:00 CST 3922]

五 参考

[NIO与Netty] ThreadLocal详解_zkp_java的专栏-CSDN博客_nio threadlocal

Java中的ThreadLocal详解 - 夏末秋涼 - 博客园

ThreadLocal内存泄漏问题解析_稚彦的博客-CSDN博客_threadlocal内存泄露

黑马程序员Java基础教程由浅入深全面解析threadlocal_哔哩哔哩_bilibili

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值