1.问题引入
多个业务模块针对同一个static变量的操作 要保证在不同线程中 各模块操作的是自身对应的变量对象
例如:
package org.lkl.thead; import java.util.Random; /** * 线程共享数据 * Function : * @author : Liaokailin * CreateDate : 2014-6-12 * version : 1.0 */ public class ThreadShareData { 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 = new Random().nextInt(); System.out.println(Thread.currentThread().getName()+ " put random data:"+data); new A().get() ; new B().get() ; } }).start() ; } } static class A { public int get(){ System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); return data ; } } static class B{ public int get(){ System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data); return data ; } } }
模块A ,B都需要访问static的变量data 在线程0中会随机生成一个data值 假设为10 那么此时模块A和模块B在线程0中得到的data的值为10 ;在线程1中 假设会为data赋值为20 那么在当前线程下
模块A和模块B得到data的值应该为20
看程序执行的结果:
Thread-0 put random data:-1344602819 Thread-1 put random data:-1842611697 A from Thread-1 get data :-1842611697 A from Thread-0 get data :-1842611697 B from Thread-1 get data :-1842611697 B from Thread-0 get data :-1842611697
在线程0中执行模块A和模块B的方法去获取data的值 但是在获取之前 线程1就将data的值给修改为-1842611697 导致线程0中的模块A和模块B获取了错误的数据.
2.解决问题
那么如何得到正确的效果呢?
可是将data数据和当前允许的线程绑定在一块,在模块A和模块B去获取数据data的时候 是通过当前所属的线程去取得data的结果就行了。
声明一个Map集合 集合的Key为Thread 存储当前所属线程 Value 保存data的值,代码如下:
package org.lkl.thead.sharedata; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * 线程共享数据 * Function : * @author : Liaokailin * CreateDate : 2014-6-12 * version : 1.0 */ public class ThreadShareData { private static int data = 0 ; private static Map<Thread,Integer> threadData = new HashMap<Thread, Integer>() ; public static void main(String[] args) { for(int i = 0 ;i<2 ;i++){ new Thread(new Runnable(){ @Override public void run() { synchronized (ThreadShareData.class) { //保证下面的代码都执行完才允许其他线程进入 data = new Random().nextInt(); threadData.put(Thread.currentThread(), data) ; //将数据绑定到带当前线程 System.out.println(Thread.currentThread().getName()+ " put random data:"+data); new A().get() ; new B().get() ; } } }).start() ; } } static class A { public int get(){ //取数据都从当前线程中取得 int d = threadData.get(Thread.currentThread()) ; System.out.println("A from " + Thread.currentThread().getName() + " get data :" + d); return data ; } } static class B{ public int get(){ //取数据都从当前线程中取得 int d = threadData.get(Thread.currentThread()) ; System.out.println("B from " + Thread.currentThread().getName() + " get data :" + d); return data ; } } }
程序执行的结果:
Thread-0 put random data:-1842492021 A from Thread-0 get data :-1842492021 B from Thread-0 get data :-1842492021 Thread-1 put random data:-1886142929 A from Thread-1 get data :-1886142929 B from Thread-1 get data :-1886142929
3 问题的拓展
将数据与当前线程相挂钩的话 那么显然是可以利用ThreadLocal这个类 那么改造上面的代码得到下面的代码:
package org.lkl.thead.sharedata; import java.util.Random; /** * 线程共享数据 * Function : * @author : Liaokailin * CreateDate : 2014-6-12 * version : 1.0 */ public class ThreadShareData { private static int data = 0 ; /** * ThreadLocal 类似于map集合 只是集合的key是当前正在运行的线程 * 通过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() { synchronized (ThreadShareData.class) { //保证下面的代码都执行完才允许其他线程进入 data = new Random().nextInt(); threadLocal.set(data) ; //将数据绑定到带当前线程 System.out.println(Thread.currentThread().getName()+ " put random data:"+data); new A().get() ; new B().get() ; } } }).start() ; } } static class A { public int get(){ //取数据都从当前线程中取得 int d = threadLocal.get() ; System.out.println("A from " + Thread.currentThread().getName() + " get data :" + d); return data ; } } static class B{ public int get(){ //取数据都从当前线程中取得 int d = threadLocal.get() ; System.out.println("B from " + Thread.currentThread().getName() + " get data :" + d); return data ; } } }
代码执行完以后会得到同样的效果。
接下来还有一个问题 上面一直讨论的是线程间共享一个变量 那么如果是多个变量呢?
要直接ThreadLocal每次只能是一个变量和当前线程挂钩 如果有多个变量要和当前线程挂钩的话 就得生命多个ThreadLocal对象 这样子做很显然是不合理的。那如何处理呢?
由于ThreadLocal可以并且只能和一个变量挂钩,那么我们可以将这个变量设置为一个变量的容器,容器中可以存在多个变量,这也就是将需要和当前线程绑定的变量封装到一个实体类中
package org.lkl.thead.sharedata; import java.util.Random; /** * 线程共享数据 * Function : * @author : Liaokailin * CreateDate : 2014-6-12 * version : 1.0 */ public class ThreadShareData { 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() { synchronized (ThreadShareData.class) { //保证下面的代码都执行完才允许其他线程进入 data = new Random().nextInt(); VariableContainer.getThreadInstance().setAge(data) ; VariableContainer.getThreadInstance().setName("liaokailin-"+data) ; System.out.println(Thread.currentThread().getName()+ " Put VariableContainer:"+VariableContainer.getThreadInstance().toString()); new A().get() ; new B().get() ; } } }).start() ; } } static class A { public int get(){ //取数据都从当前线程中取得 System.out.println("A from " + Thread.currentThread().getName() + " get data :" + VariableContainer.getThreadInstance().toString()); return data ; } } static class B{ public int get(){ //取数据都从当前线程中取得 System.out.println("B from " + Thread.currentThread().getName() + " get data :" + VariableContainer.getThreadInstance().toString()); return data ; } } } /** * 变量容器 */ class VariableContainer{ private String name ; private int age ; private static ThreadLocal<VariableContainer> threadLocal = new ThreadLocal<VariableContainer>() ; /** * 获取当前线程中绑定的变量容器 外部可以往容器中设值 */ public static VariableContainer getThreadInstance(){ VariableContainer container = threadLocal.get() ; if(container==null){ container = new VariableContainer() ; threadLocal.set(container) ; } return container ; } 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 "VariableContainer [name=" + name + ", age=" + age + "]"; } }
执行结果:
Thread-0 Put VariableContainer:VariableContainer [name=liaokailin--2103275506, age=-2103275506] A from Thread-0 get data :VariableContainer [name=liaokailin--2103275506, age=-2103275506] B from Thread-0 get data :VariableContainer [name=liaokailin--2103275506, age=-2103275506] Thread-1 Put VariableContainer:VariableContainer [name=liaokailin-816504398, age=816504398] A from Thread-1 get data :VariableContainer [name=liaokailin-816504398, age=816504398] B from Thread-1 get data :VariableContainer [name=liaokailin-816504398, age=816504398]