多线程:线程内局部变量

ThreadLocal类

该类提供了线程内局部 (thread-local) 变量。

好比有两个窗口(两个线程),一个窗口可以拿饮料,一个窗口可以拿食物。现在有多个人要来拿东西,如果在饮料窗口有的人拿到了饮料,有的人拿到了不该拿的食物,就说明线程之间出现了混乱,我们应当避免这种情况出现。

以下代码就可能会出现线程混乱的问题:

private static int data;

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();
                ThreadLocalDemo.data = data;
                System.out.println(Thread.currentThread().getName()+"上的数据是:"+ data);
                //模块A获取数据
                System.out.println(Thread.currentThread().getName()+"上A模块取得数据:"+ new A().get());
                //模块B获取数据
                System.out.println(Thread.currentThread().getName()+"上B模块取得数据:"+ new B().get());

            }
        }).start();

    }
}

//模块A
static class A{
    public static int get(){
        return data;
    }
}
//模块B
static class B{
    public static int get(){
        return data;
    }
}

随机的打印结果如下:

Thread-0上的数据是:-312244177

Thread-1上的数据是:-1541655481

Thread-0上A模块取得数据:-312244177

Thread-1上A模块取得数据:-312244177

Thread-0上B模块取得数据:-312244177

Thread-1上B模块取得数据:-312244177 

可以看出,在线程1上取到了线程0的数据,说明线程之间产生了混乱。

set方法和get方法

不再通过成员变量来存储值了,而是使用ThreadLocal类的set方法来存储值,再直接通过get方法来获取值。set方法传递存储的数据,在线程内使用get方法无需传递参数,默认就是当前线程。

//创建ThreadLocal对象,泛型为要存储的数据的类型
private static ThreadLocal<Integer> local = 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();
                //使用ThreadLocal的set方法将数据存到底层的Map集合中
                local.set(data);
                System.out.println(Thread.currentThread().getName()+"上的数据是:"+ data);
                //模块A获取数据
                System.out.println(Thread.currentThread().getName()+"上A模块取得数据:"+ new A().get());
                //模块B获取数据
                System.out.println(Thread.currentThread().getName()+"上B模块取得数据:"+ new B().get());

            }
        }).start();

    }
}

//模块A
static class A{
    public static int get(){
        return local.get();
    }
}
//模块B
static class B{
    public static int get(){
        return local.get();
    }
}

set底层

在 set 方法执行时,会首先根据当前线程获取当前线程的ThreadLocalMap对象,然后往这个map中插入一条记录:key 其实是ThreadLocal对象,用于记录当前线程唯一的局部标识符(在第一次自动调用 UniqueThreadIdGenerator.getCurrentThreadId()时分配的,在后续调用中不会更改。),value是各自的set方法传进去的值。这样就记录了线程和当前线程中数据的映射绑定关系。

一个ThreadLocal对象可以多个线程共用。可以直接看做是一个Map容器,用来存储当前线程和对应值的键值对。因为键的唯一性,所以一个ThreadLocal对象在一个线程内只能存储一个数据;如果要在一个线程内存储多条数据,就需要创建多个ThreadLocal对象来存取数据,比较麻烦。

在线程结束时可以调用 ThreadLocal.remove()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的 ThreadLocal 变量。

存储多个数据

可以将一个线程内的多个数据封装成一个实体类,然后使用set方法存入一个实体类对象,再取出一个实体类对象。通常使用单例模式来设计该实体类对象,并将实体类和ThreadLocal对象进行绑定。

我们可以将ThreadLocal隐藏起来,使其对外部而言只用关注数据即可。优雅的设计如下:

public class ThreadLocalMap2 {

    public static void main(String[] args) {

            new Thread(new Runnable() {
                @Override
                public void run() {
                    //将数据封装到User对象中
                    User user = User.getUser();
                    user.setName("张三");
                    user.setAge(20);
                    //模块A获取数据
                    new A().get();
                    //模块B获取数据
                    new B().get();

                }
            }).start();
    }

    //模块A
    static class A{
        public static void get(){
            User user = User.getUser();
            System.out.println("A模块获取的姓名是:"+user.getName()+",年龄是:"+user.getAge());
        }
    }
    //模块B
    static class B{
        public static void get(){
            User user = User.getUser();
            System.out.println("B模块获取的姓名是:"+user.getName()+",年龄是:"+user.getAge());
        }
    }

    //存储数据的实体类
    static class User{
        private String name;
        private Integer age;
        private static ThreadLocal<User> local = new ThreadLocal<>();

        private User() {
        }

        public synchronized static User getUser() {
            //让一开始获取的User就在ThreadLocal中
            User user = local.get();
            if(user == null){
                user = new User();
                local.set(user);
            }
            return user;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
    }
}

 


版权声明:本文为caozp913原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。