网资酷

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 98|回复: 0

Java并发编程---ThreadLocal

[复制链接]

2

主题

6

帖子

10

积分

新手上路

Rank: 1

积分
10
发表于 2023-1-14 12:00:35 | 显示全部楼层 |阅读模式
一、什么是ThreadLocal?

多线程共享变量的维护是非常头痛的问题,采用乐观悲观策略,悲观策略简单地做法我们可以对共享变量加锁实现,但是锁的开销是比较大的,因此我们也可以通过乐观策略,采用类似CAS(Compare And Set)的方法进行维护,当然,在读多写少的情况下,我们还可以采用Copy-On-Write写时复制的来控制共享变量,其中最经典的实现那就是JDK的CopyOnWriteArrayList了。
好了,说了这么多,下面正式进入正题。我们可以想这么一个问题,在多线程环境中如何对每一条线程的生命周期进行跟踪,跟具体地说,在web项目中,我们通常会对某一个请求从进入系统到退出系统的整个生命周期进行日志记录,又或者说是对一个事务的全过程进行跟踪记录,通常的做法是采用为每个线程建立一个叫transactionId的值,用于标识每个线程并进程跟踪,下图红圈即为transactionId的值。



那么你可能会说,我也可以不使用transactionId来跟踪啊,我采用线程名不就好了吗?道理是这样,但是当你想对改值进行自定义呢?比如获取客户端的ip地址、被调用的接口名呢???
好了,假设我们采用transactionId来跟踪线程,那么如果这个transactionId是个共享变量的话,那我们不就是得对其做相关线程同步的操作,线程那么多,那不烦死,笨死!嗯,TheadLocal就是在这种情况下而生了,一个TheadLocal代表一个变量,你千万不要以为它代表一个线程,不然我会晕死!这个变量天生就是线程安全的。为什么呢?因为它的工作原理是会为每条线程做一份变量的拷贝,各个线程的变量不会相互影响,自然就不会有线程安全问题咯!
酱紫,我画张图吧!



TheadLocal的底层实现原理是通过ThreadLocalMap实现,时间匆忙,其原理不在本文讲解范围内,读者自行学习源码!还有一点思想值得学习,多线程一般的同步机制采用了“以时间换空间”的方式,比如定义一个static变量,同步访问,而ThreadLocal则采用了“以空间换时间”的方式。
二、实例解析

package com.wokao66.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalTest {
    //线程本地变量
    private static ThreadLocal<Integer> sessionId = new ThreadLocal<>();
    public static void main(String[] args) {
        //线程1
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //我对ThreadLocal的值进行修改,自定义
                sessionId.set(1);
                try {
                    Thread.sleep(1000);
                    //模拟一个线程的生命周期多次获取ThreadLocal变量的值,验证是否在当前线程有做一份拷贝
                    for (int i = 0; i < 5; i++) {
                        System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
                    }
                } catch (InterruptedException e) {}
            }
        });
        //线程2
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //我对ThreadLocal的值进行修改,自定义
                sessionId.set(2);
                try {
                    Thread.sleep(1000);
                    //模拟一个线程的生命周期多次获取ThreadLocal变量的值,验证是否在当前线程有做一份拷贝
                    for (int i = 0; i < 5; i++) {
                        System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
                    }
                } catch (InterruptedException e) {}
            }
        });
        //线程1
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                //我对ThreadLocal的值进行修改,自定义
                sessionId.set(3);
                try {
                    Thread.sleep(1000);
                    //模拟一个线程的生命周期多次获取ThreadLocal变量的值,验证是否在当前线程有做一份拷贝
                    for (int i = 0; i < 5; i++) {
                        System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
                    }
                } catch (InterruptedException e) {}
            }
        });
        ExecutorService service = Executors.newFixedThreadPool(30);
        service.execute(thread1);
        service.execute(thread2);
        service.execute(thread3);
    }
}控制台输出
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-2]2
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3可以看到每条线程对应的ThreadLocal是一样的,证明了确实是有做拷贝操作!
三、ThreadLocal的应用场景

使用ThreadLocal首先一点那就是对变量的访问不需要同步控制,常用的场景如下:

  • 1、对多线程进行跟踪,常用于接口访问日志记录,可以参考MDC机制
  • 2、用来解决数据库连接、Session管理(其实也是一种跟踪用途)
需要注意的是,既然TreadLocal是以采用空间换取时间的思想,所以可以想象TreadLocal并不适合来定义大对象,因为大对象的话每个线程都有拷贝,线程一多,性能必定受到牵连,甚至JVM抛出ERROR
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|网资酷

GMT+8, 2025-3-15 05:28 , Processed in 0.078261 second(s), 22 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表