美文网首页多线程
线程安全2 - ThreadLocal:线程范围的共享变量

线程安全2 - ThreadLocal:线程范围的共享变量

作者: 小超_8b2f | 来源:发表于2019-09-29 11:05 被阅读0次

见下页的示意图和代码,解释ThreadLocal的作用和目的:用于实现线程内数据共享,即对于相同的程序代码,多个模块在同一个线程中运行需要共享一份数据,而在另外线程中运行时又共享另外一份数据。


image.png

每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

ThreadLocal应用场景

订单处理包含一系列操作:减少库存量,增加一条流水账,修改总账。这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理。如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则提交所有的操作。这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块中。

银行转账包含一系列操作:把转出的账户余额减少、把转入的账号余额增加。这2个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象。转入和转出操作的代码分别位于2个不同账户对象的方法。

例如Struts2的ActionContext,同一段代码被不同的线程调用运行时,改代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext()拿到的Context对象都不相同,对同一个线程来说,不管调用getContext()多少次和在哪个模块中调用getContext(),拿到的都是同一个。

  • 实验案例:定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量。
  • 实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量
    1. 对基本类型的数据进行封装,这种应用相对很少见
    2. 对对象类型的数据进行封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。

  • 总结:一个ThreadLocal代表一个变量,故其中只能放一个数据,你有2个变量都要线程内共享,则要定义2个ThreadLocal对象,如果有一百个变量需要线程共享呢?那请先定义一个对象来装这一百个变量,然后在ThreadLocal中存储这一个对象。

  1. 先用全局变量做实验,看第一个线程取到的不是第一个线程放入的数据,而是第二个线程放入的数据。
  2. 用map演示线程范围内共享数据的原理。
  3. 先在MyThreadLocalData中定义一个访问权限为public的ThreadLocal类型的变量x,直接对这个x对象进行读写操作。
  4. 将MyThreadLocalData类自身变成一个具有业务功能的对象,每个线程仅能有该类的一个实例对象,即对于不同的线程来说,MyThreadLocalData.getMyData()静态方法拿到的对象都不相同,但对于同一个线程来说,不管调用MyThreadLocalData.getMyData()多少次和在哪里调用,拿到的都是同一个MyThreadLocalData对象。

先将MyThreadLocalData对象封装成具有业务功能的对象,
然后设计getMyData()方法的定义,
最后定义getMyData()要操作的ThreadLocal变量和编写具体的代码。


创建3个线程,他们都访问了3个对象,第一个对象设置值,第二三个对象取值,同一个线程设置的值,只能被相同的线程获取。

1. 示例程序(❌)

import java.util.Random;

public class ThreadScopeShareData {
    
    static Random random = new Random();
    
    private static int data = 0;

    public static void main(String[] args) {
        for(int i = 0; i < 2; i++)
            new Thread(new Runnable() {
                @Override
                public void run() {
                    data = random.nextInt();
                    System.out.println(Thread.currentThread().getName() + " has put data : " + data);
                    
                    new A().get();
                    new B().get();
                }
            }).start();
    }

    static class A {
        public int get() {
            System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
    
    static class B {
        public int get() {
            System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
}

结果:

Thread-0 has put data : -596965467
Thread-1 has put data : -1853114354
A from Thread-0 has put data : -1853114354
A from Thread-1 has put data : -1853114354
B from Thread-1 has put data : -1853114354
B from Thread-0 has put data : -1853114354

这结果明显不是想要的。应该2个线程在A、B逻辑中get()的数据分别不同才对。

image.png

2. 改进版(Map版本):

import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

public class ThreadScopeShareData {
    
    static Random random = new Random();
    
    private static Map<Thread,Integer> threadData = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        for(int i = 0; i < 2; i++)
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = random.nextInt();
                    System.out.println(Thread.currentThread().getName() + " has put data : " + data);
                    threadData.put(Thread.currentThread(), data);
                    
                    new A().get();
                    new B().get();
                }
            }).start();
    }

    static class A {
        public int get() {
            int data = threadData.get(Thread.currentThread());
            System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
    
    static class B {
        public int get() {
            int data = threadData.get(Thread.currentThread());
            System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
}

Thread-0 has put data : 431467914
Thread-1 has put data : 1116409612
A from Thread-0 has put data : 431467914
A from Thread-1 has put data : 1116409612
B from Thread-0 has put data : 431467914
B from Thread-1 has put data : 1116409612

3.改进版(ThreadLocal版本)

package com.everjiankang.unit1;

import java.util.Random;

public class ThreadLocalTest {
    
    static Random random = new Random();
    
    private static ThreadLocal<Integer> threadData = new ThreadLocal<>();

    public static void main(String[] args) {
        for(int i = 0; i < 2; i++)
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = random.nextInt();
                    System.out.println(Thread.currentThread().getName() + " has put data : " + data);
                    threadData.set(data); //放到本线程里
                    
                    new A().get();
                    new B().get();
                }
            }).start();
    }

    static class A {
        public int get() {
            int data = threadData.get(); //当前线程所set的数据
            System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
    
    static class B {
        public int get() {
            int data = threadData.get();//当前线程所set的数据
            System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
}

结果:

Thread-0 has put data : 687235319
Thread-1 has put data : 1082718931
A from Thread-0 has put data : 687235319
A from Thread-1 has put data : 1082718931
B from Thread-0 has put data : 687235319
B from Thread-1 has put data : 1082718931

4. ThreadLocal存储非基本类型

package com.everjiankang.unit1;

import java.util.Random;

public class ThreadLocalTest2 {
    
    static Random random = new Random();
    
    private static ThreadLocal<User> threadData = new ThreadLocal<>();

    public static void main(String[] args) {
        for(int i = 0; i < 2; i++)
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = random.nextInt();
                    
                    User user = new User();
                    user.setName("xiaochao " + data);
                    user.setAge(data);
                    
                    System.out.println(Thread.currentThread().getName() + " has put data : " + user);
                    System.out.println();
                    threadData.set(user); //放到本线程里
                    
                    new A().get();
                    new B().get();
                }
            }).start();
    }

    static class A {
        public User get() {
            User data = threadData.get(); //当前线程所set的数据
            System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
    
    static class B {
        public User get() {
            User data = threadData.get();//当前线程所set的数据
            System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
}

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;}
    
    @Override
    public String toString() {return "User [name=" + name + ", age=" + age + "]";}
}

结果:

Thread-1 has put data : User [name=xiaochao 811252830, age=811252830]

Thread-0 has put data : User [name=xiaochao 1834330439, age=1834330439]

A from Thread-0 has put data : User [name=xiaochao 1834330439, age=1834330439]
A from Thread-1 has put data : User [name=xiaochao 811252830, age=811252830]
B from Thread-0 has put data : User [name=xiaochao 1834330439, age=1834330439]
B from Thread-1 has put data : User [name=xiaochao 811252830, age=811252830]

改进1:单例模式获取User(❌)

单例无论多少个线程请求,都只有一个对象,所以无法实现每个线程一个实例

package com.everjiankang.unit1;

import java.util.Random;

public class ThreadLocalTest2 {
    
    static Random random = new Random();
    
    private static ThreadLocal<User2> threadData = new ThreadLocal<>();

    public static void main(String[] args) {
        for(int i = 0; i < 2; i++)
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = random.nextInt();
                    
                    User2 user = User2.getInstance();
                    user.setName("xiaochao " + data);
                    user.setAge(data);
                    
                    System.out.println(Thread.currentThread().getName() + " has put data : " + user);
                    System.out.println();
                    threadData.set(user); //放到本线程里
                    
                    new A().get();
                    new B().get();
                }
            }).start();
    }

    static class A {
        public User2 get() {
            User2 data = threadData.get(); //当前线程所set的数据
            System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
    
    static class B {
        public User2 get() {
            User2 data = threadData.get();//当前线程所set的数据
            System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
}


class User2 {
    
    private User2() {}
    
    private static User2 user = null;//new User();
    
    public static synchronized User2 getInstance() {
        if(user == null) {
            user = new User2();
        }
        return 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;}
    
    @Override
    public String toString() {return "User2 [name=" + name + ", age=" + age + "]";}
}

结果:

Thread-0 has put data : User2 [name=xiaochao 1219775690, age=1219775690]

Thread-1 has put data : User2 [name=xiaochao 1219775690, age=1219775690]

A from Thread-1 has put data : User2 [name=xiaochao 1219775690, age=1219775690]
A from Thread-0 has put data : User2 [name=xiaochao 1219775690, age=1219775690]
B from Thread-1 has put data : User2 [name=xiaochao 1219775690, age=1219775690]
B from Thread-0 has put data : User2 [name=xiaochao 1219775690, age=1219775690]

继续改进2:ThreadLocal形式的单例,更优雅

package com.everjiankang.unit1;

import java.util.Random;

public class ThreadLocalTest2 {
    
    static Random random = new Random();
    
    public static void main(String[] args) {
        for(int i = 0; i < 2; i++)
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = random.nextInt();
                    
                    User.getInstance().setName("xiaochao " + data);
                    User.getInstance().setAge(data);
                    
                    System.out.println(Thread.currentThread().getName() + " has put data : " + User.getInstance());
                    System.out.println();
                    new A().get();
                    new B().get();
                }
            }).start();
    }

    static class A {
        public User get() {
            User data = User.getInstance(); //当前线程所set的数据
            System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
    
    static class B {
        public User get() {
            User data = User.getInstance();//当前线程所set的数据
            System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
            return data;
        }
    }
}
/** ThreadLocal只在这里封装,更优雅,单例*/
class User {
    
    private User() {}
    
    private static ThreadLocal<User> map = new ThreadLocal<User>();//new User();
    /**实现了ThreadLocal级别的单例*/
    public static User getInstance() {
        if(map.get() == null) {
            map.set(new User());
        }
        return map.get();
    }
    
    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;}

    @Override
    public String toString() {return "User [name=" + name + ", age=" + age + "]";}
}

相关文章

网友评论

    本文标题:线程安全2 - ThreadLocal:线程范围的共享变量

    本文链接:https://www.haomeiwen.com/subject/ysbmectx.html