【并发技术07】使用ThreadLocal在线程范围内共享数据

简介: 【并发技术07】使用ThreadLocal在线程范围内共享数据

在上一篇文章中我们总结了一下,线程范围内的数据共享问题,即定义一个 Map,将当前线程名称和线程中的数据以键值对的形式存到 Map 中,然后在当前线程中使用数据的时候就可以根据当前线程名称从 Map 中拿到当前线程中的数据,这样就可以做到不同线程之间数据互不干扰。其实 ThreadLocal 类就是给我们提供了这个解决方法,所以我们完全可以用 ThreadLocal 来完成线程范围内数据的共享。

public class ThreadScopeShareData {
    //定义一个ThreadLocal
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
    public static void main(String[] args) {
        for(int i = 0; i < 2; i ++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() + " has put a data: " + data);
                    threadLocal.set(data);//直接往threadLocal里面里面扔数据即可
                    new TestA().getData();
                    new TestB().getData();
                }
            }).start();
        }
    }
    static class TestA {
        public void getData() {
            System.out.println("A get data from " + Thread.currentThread().getName() + ": " + threadLocal.get());//直接取,不用什么关键字,它直接从当前线程中取
        }
    }
    static class TestB {
        public void getData() {
            System.out.println("B get data from " + Thread.currentThread().getName() + ": " + threadLocal.get());//直接取,不用什么关键字,它直接从当前线程中取
        }
    }
}

结合上一节的代码,可以看出,其实 ThreadLocal 就相当于一个 Map,只不过我们不需要设定 key 了,它默认就是当前的 Thread,往里面放数据,直接 set 即可,取数据,直接 get 即可,很方便,就不用 Map 一个个存了,但是问题来了, ThreadLocal 虽然存取方便,但是 get() 方法中根本没有参数,也就是说我们只能往 ThreadLocal 中放一个数据,多了就不行了,那么该如何解决这个问题呢?


很明显, ThreadLocal 是个容器,且只能存一下,那么如果有多个数据,我们可以定义一个类,把数据都封装到这个类中,然后扔到 ThreadLocal 中,用的时候取这个类,再从类中去我们想要的数据即可。


好,现在有两个线程,每个线程都要操作各自的数据,而且数据有两个:名字和年龄。根据上面的思路,写一个 demo,如下:

public class ThreadScopeShareData {
    private static ThreadLocal<User> threadLocal = new ThreadLocal<User>(); 
    public static void main(String[] args) {
        for(int i = 0; i < 2; i ++) {//开启两个线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() + " has put a data: " + data);
                    //每个线程中维护一个User,User中保存了name和age
                    User user = new User();
                    user.setName("name" + data);
                    user.setAge(data);
                    threadLocal.set(user); //向当前线程中存入user对象
                    new TestA().getData();
                    new TestB().getData();
                }
            }).start();
        }
    }
    static class TestA {
        public void getData() {
            User user = threadLocal.get();//从当前线程中取出user对象
            System.out.println("A get data from " + Thread.currentThread().getName() + ": " 
                    + user.getName() + "," + user.getAge());
        }
    }
    static class TestB {
        public void getData() {
            User user = threadLocal.get();//从当前线程中取出user对象
            System.out.println("B get data from " + Thread.currentThread().getName() + ": " 
                    + user.getName() + "," + user.getAge());
        }
    }
}
//定义一个User类来存储姓名和年龄
class User {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }   
}

这样进行一下封装就可以实现多个数据的存储了,但是上面这个程序是不太好的,原因很明显,在线程中,我要自己 new 一个对象,然后对其进行操作,最后还得把这个对象扔到当前线程中。这不太符合设计的思路,设计的思路应该是这样的,不能让用户自己去 new 啊,如果有个类似于 getThreadInstance() 的方法,用户想要从 ThreadLocal 中拿什么对象就用该对象去调用这个 getThreadInstance() 方法多好,这样拿到的永远都是本线程范围内的对象了。


这让我想到了学习 JDBC 的时候,从 ThreadLocal 中拿 connection 时的做法了,如果当前 ThreadLocal 中有就拿出来,没有就产生一个,这跟这里的需求是一样的,我想要一个 User,那我应该用 User 去调用 getThreadLInstance() 方法获取本线程中的一个 User 对象,如果有就拿,如果没有就产生一个。完全一样的思路。这个设计跟单例的模式有点像,这里说有点像不是本质上像,是代码结构很像。先看一下简单的单例模式代码结构:

public class Singleton {
    private static Singleton instance = null;
    private Singleton() {//私有构造方法阻止外界new        
    }
    public static synchronized Singleton getInstance() {  //提供一个公共方法返回给外界一个单例的实例
        if (instance == null) {  //如果没有实例
            instance = new Singleton();  //就新new一个
        }  
        return instance;  //返回该实例
    } 
}

这是懒汉式单例模式的代码结构,我门完全可以效仿该思路去设计一个从当前线程中拿 User 的办法,所以将程序修改如下:

public class ThreadScopeShareData {
//不需要在外面定义threadLocal了,放到User类中了
//    private static ThreadLocal<User> threadLocal = new ThreadLocal<User>();
    public static void main(String[] args) {
        for(int i = 0; i < 2; i ++) {
            new Thread(new Runnable() {             
                @Override
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() + " has put a data: " + data);
                    //这里直接用User去调用getThreadLocal这个静态方法获取本线程范围内的一个User对象
                    //这里就优雅多了,我完全不用关心如何去拿该线程中的对象,如何把对象放到threadLocal中
                    //我只要拿就行,而且拿出来的肯定就是当前线程中的对象,原因看下面User类中的设计
                    User.getThreadInstance().setName("name" + data);
                    User.getThreadInstance().setAge(data);
                    new TestA().getData();
                    new TestB().getData();
                }
            }).start();
        }
    }
    static class TestA {
        public void getData() {
            //还是调用这个静态方法拿,因为刚刚已经拿过一次了,threadLocal中已经有了
            User user = User.getThreadInstance();
            System.out.println("A get data from " + Thread.currentThread().getName() + ": " 
                    + user.getName() + "," + user.getAge());
        }
    }
    static class TestB {
        public void getData() {         
            User user = User.getThreadInstance();
            System.out.println("A get data from " + Thread.currentThread().getName() + ": " 
                    + user.getName() + "," + user.getAge());
        }
    }
}
class User {    
    private User() {}
    private static ThreadLocal<User> threadLocal = new ThreadLocal<User>();
    //注意,这不是单例,每个线程都可以new,所以不用synchronized,
    //但是每个threadLocal中是单例的,因为有了的话就不会再new了
    public static /*synchronized*/ User getThreadInstance() {
        User instance = threadLocal.get(); //先从当前threadLocal中拿
        if(instance == null) {
            instance = new User();
            threadLocal.set(instance);//如果没有就新new一个放到threadLocal中
        }
        return instance; //向外返回该User
    }
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

经过这样的改造,代码就优雅多了,外界从来不要考虑如何去当前线程中拿数据,只要拿就行,拿出来的肯定就是当前线程中你想要的对象,因为在对象内部已经写好了这个静态方法了,而且拿出来 操作完了后,也不需要再放到 threadLocal 中,因为它本来就在 threadLocal 中,这就封装的相当好了。ThreadLocal 类的应用和使用技巧就总结这么多吧~


相关文章
|
1月前
|
消息中间件 监控 Java
线程池关闭时未完成的任务如何保证数据的一致性?
保证线程池关闭时未完成任务的数据一致性需要综合运用多种方法和机制。通过备份与恢复、事务管理、任务状态记录与恢复、数据同步与协调、错误处理与补偿、监控与预警等手段的结合,以及结合具体业务场景进行分析和制定策略,能够最大程度地确保数据的一致性,保障系统的稳定运行和业务的顺利开展。同时,不断地优化和改进这些方法和机制,也是提高系统性能和可靠性的重要途径。
119 62
|
1月前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
198 59
|
1月前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
41 6
|
29天前
|
存储 监控 安全
深入理解ThreadLocal:线程局部变量的机制与应用
在Java的多线程编程中,`ThreadLocal`变量提供了一种线程安全的解决方案,允许每个线程拥有自己的变量副本,从而避免了线程间的数据竞争。本文将深入探讨`ThreadLocal`的工作原理、使用方法以及在实际开发中的应用场景。
56 2
|
1月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
58 6
|
1月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
1月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
2月前
|
缓存 安全 Java
使用 Java 内存模型解决多线程中的数据竞争问题
【10月更文挑战第11天】在 Java 多线程编程中,数据竞争是一个常见问题。通过使用 `synchronized` 关键字、`volatile` 关键字、原子类、显式锁、避免共享可变数据、合理设计数据结构、遵循线程安全原则和使用线程池等方法,可以有效解决数据竞争问题,确保程序的正确性和稳定性。
63 2
|
2月前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
36 1
|
2月前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
86 0