Java并发编程入门(二十二)ThreadLocal变量

389 阅读5分钟

banner窄.png

铿然架构  |  作者  /  铿然一叶 这是铿然架构的第 87 篇原创文章

相关阅读:

Java并发编程(一)知识地图
Java并发编程(二)原子性
Java并发编程(三)可见性
Java并发编程(四)有序性
Java并发编程(五)创建线程方式概览
Java并发编程入门(六)synchronized用法
Java并发编程入门(七)轻松理解wait和notify以及使用场景
Java并发编程入门(八)线程生命周期
Java并发编程入门(九)死锁和死锁定位
Java并发编程入门(十)锁优化
Java并发编程入门(十一)限流场景和Spring限流器实现
Java并发编程入门(十二)生产者和消费者模式-代码模板
Java并发编程入门(十三)读写锁和缓存模板
Java并发编程入门(十四)CountDownLatch应用场景
Java并发编程入门(十五)CyclicBarrier应用场景
Java并发编程入门(十六)秒懂线程池差别
Java并发编程入门(十七)一图掌握线程常用类和接口
Java并发编程入门(十九)异步任务调度工具CompleteFeature
Java并发编程入门(二十)常见加锁场景和加锁工具
# Java并发编程入门(二十一)volatile关键字


1. Java变量的作用域

1.1. Java类中变量的作用域

从Java类的角度来看,Java变量的作用域有如下几种:

import java.util.concurrent.TimeUnit;

public class VariableScopeDemo {
    private static int classVariable;  // 类变量,作用范围为JVM进程
    private int instanceVariable;      // 类实例变量,作用范围为类实例内部

    public static void increaseClassVariable() {
        log("classVariable++");
        classVariable++;
    }

    public static int getClassVariable() {
        return classVariable;
    }

    public void increaseInstanceVariable() {
        instanceVariable++;
    }

    public int getInstanceVariable() {
        return instanceVariable;
    }

    public int increaseBlockVariable() {
        int blockVariable = 0;  // 代码块级别的变量,只在代码块内可见
        blockVariable++;
        return blockVariable;
    }

    private static class Thread1 extends Thread {
        @Override
        public void run() {
            VariableScopeDemo.increaseClassVariable();  // 修改了类级别的变量,在另外一个线程中可以看到修改后的值
        }
    }

    private static class Thread2 extends Thread {
        @Override
        public void run() {
            log("classVariable: " + VariableScopeDemo.getClassVariable());
        }
    }

    private static void log(String msg) {
        System.out.println(msg);
    }

    private static void quietlySleep() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch(InterruptedException e) {
            // nothing to do
        }
    }

    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
        thread1.start();
        quietlySleep();  // 确保thread1先执行
        thread2.start();

        VariableScopeDemo demo1 = new VariableScopeDemo();
        VariableScopeDemo demo2 = new VariableScopeDemo();
        demo1.increaseInstanceVariable();   // 不会更改demo2的值
        int instance1Variable = demo1.getInstanceVariable();  // 可以获得本实例修改后的值
        int instance2Variable = demo2.getInstanceVariable();
        log("demo1 instanceVariable: " + instance1Variable);
        log("demo2 instanceVariable: " + instance2Variable);

        int instance1BlockVariable = demo1.increaseBlockVariable();
        int instance2BlockVariable = demo2.increaseBlockVariable();
        log("demo1 instance1BlockVariable: " + instance1BlockVariable);
        log("demo2 instance2BlockVariable: " + instance2BlockVariable);
    }
}

打印日志:

classVariable++
demo1 instanceVariable: 1
demo2 instanceVariable: 0
demo1 instance1BlockVariable: 1
demo2 instance2BlockVariable: 1
classVariable: 1

1.2. 应用中变量的作用域

这里的应用包括web应用和JAVA进程,在应用中Java变量的作用范围有:

1.JVM变量 - 在Java进程中共享,就是上面提到的类变量

2.Application变量 - 在整个web应用中共享,只要后台应用存在,则一直存在

3.Session变量 - 在session中共享,浏览器打开某个网站则存在,浏览器关闭该网站则消失,不同的登录用户Session不同

4.线程变量 - 在线程中共享,即今天将要介绍的ThreadLocal

2. ThreadLocal应用场景

ThreadLocal变量使得不需要通过参数在不同的Java类之间传递参数,这为打印trace信息提供了便利,可以把业务无关的参数都放到ThreadLocal变量中,例如TraceId、员工号等等,在打印trace信息时再从ThreadLocal中获取。

如下图所示,ThreadLocal变量可以在一次业务请求线程中的多个类中传递:

threadlocal.jpg

2.1. 参数传递例子

public class ThreadLocalParamDemo {

    private static class Controller {
        public static void createUser(String traceId, String user) {
            log(String.format("[%s] Controller.createUser be called. user: [%s]", traceId, user));
            Service.createUser(traceId, user);
        }

    }

    private static class Service {
        public static void createUser(String traceId, String user) {
            log(String.format("[%s] Service.createUser be called. user: [%s]", traceId, user));
            Dao.createUser(traceId, user);
        }
    }

    private static class Dao {
        public static void createUser(String traceId, String user) {
            log(String.format("[%s] Dao.createUser be called. user: [%s]", traceId, user));
        }
    }

    private static void log(String msg) {
        System.out.println(msg);
    }

    public static void main(String[] args) {
        String traceId = "10001";
        String user = "Jack";
        Controller.createUser(traceId, user);

        traceId = "10002";
        user = "Leo";
        Controller.createUser(traceId, user);
    }
}

打印日志:

[10001] Controller.createUser be called. user: [Jack]
[10001] Service.createUser be called. user: [Jack]
[10001] Dao.createUser be called. user: [Jack]
[10002] Controller.createUser be called. user: [Leo]
[10002] Service.createUser be called. user: [Leo]
[10002] Dao.createUser be called. user: [Leo]

2.2. ThreadLocal例子

public class ThreadLocalDemo {
    private static long traceId = 10000;
    private static ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();

    private static class Controller {
        public static void createUser(String user) {
            log(String.format("[%s] Controller.createUser be called. user: [%s]", traceIdThreadLocal.get(), user));
            Service.createUser(user);
        }

    }

    private static class Service {
        public static void createUser(String user) {
            log(String.format("[%s] Service.createUser be called. user: [%s]", traceIdThreadLocal.get(), user));
            Dao.createUser(user);
        }
    }

    private static class Dao {
        public static void createUser(String user) {
            log(String.format("[%s] Dao.createUser be called. user: [%s]", traceIdThreadLocal.get(), user));
        }
    }

    private static void log(String msg) {
        System.out.println(msg);
    }

    private static void nextTraceId() {
        traceId++;
        traceIdThreadLocal.set(String.valueOf(traceId));
    }

    private static class CreateUserThread extends Thread {
        private String user;
        public CreateUserThread(String user) {
            this.user = user;
        }

        @Override
        public void run() {
            nextTraceId();  //线程内设置,只有当前线程可见
            Controller.createUser(user);
        }
    }

    public static void main(String[] args) {
        CreateUserThread createUserThread1 = new CreateUserThread("Jack");
        createUserThread1.start();

        CreateUserThread createUserThread2 = new CreateUserThread("Leo");
        createUserThread2.start();
    }
}

打印日志:

[10002] Controller.createUser be called. user: [Leo]
[10001] Controller.createUser be called. user: [Jack]
[10002] Service.createUser be called. user: [Leo]
[10001] Service.createUser be called. user: [Jack]
[10002] Dao.createUser be called. user: [Leo]
[10001] Dao.createUser be called. user: [Jack]

2.3. 父子线程是否共享ThreadLocal变量

public class ThreadLocalChildDemo {
    private static ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();

    private static class ChildThread extends Thread {
        @Override
        public void run() {
            System.out.println(traceIdThreadLocal.get());
        }
    }

    public static void main(String[] args) {
        traceIdThreadLocal.set("10001");
        ChildThread childThread = new ChildThread();
        childThread.start();
    }
}

打印日志:

null

说明ThreadLocal变量只在同一个线程内共享。

2.4. 总结ThreadLocal的优点

1.线程安全,ThreadLocal是线程级变量,不会跨线程访问,因此线程安全。

2.分离关注点,将业务不需要关注的参数独立出来,业务代码中只需要统一调用通用的trace方法则可。

3.通过ThreadLocal在线程内传递变量,不会因为方法参数变化而修改业务方法,保持方法稳定。


<--阅过留痕,左边点赞!