Java并发06---ThreadLocal类以及应用技巧

本文详细介绍了Java中ThreadLocal类的使用方法及技巧,通过示例代码展示了如何利用ThreadLocal实现线程范围内的数据共享,并提供了封装技巧以简化数据存取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                                                                                Java并发06---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());//直接取,不用什么关键字,它直接从当前线程中取
        }
    }
}
 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

      结合上一节的代码,可以看出,其实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;
        }   
    }
     
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64

        这样进行一下封装就可以实现多个数据的存储了,但是上面这个程序是不太好的,原因很明显,在线程中,我要自己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;  //返回该实例
          } 
      }
       
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11

          这是懒汉式单例模式的代码结构,我门完全可以效仿该思路去设计一个从当前线程中拿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;
            }
        }
         
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47
          • 48
          • 49
          • 50
          • 51
          • 52
          • 53
          • 54
          • 55
          • 56
          • 57
          • 58
          • 59
          • 60
          • 61
          • 62
          • 63
          • 64
          • 65
          • 66
          • 67
          • 68
          • 69
          • 70
          • 71
          • 72
          • 73
          • 74
          • 75
          • 76
          • 77
          • 78

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


          评论
          添加红包

          请填写红包祝福语或标题

          红包个数最小为10个

          红包金额最低5元

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

          抵扣说明:

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

          余额充值