package cn.itcast.thread; import java.util.Random; public class ThreadLocalDemo3 { /** * ThreadLocal类的使用。 * * 一个ThreadLocal只代表一个变量, * 但是如果我们的数据很多我们应该如何操作了? * 我们可以把我们的数据封装到一个对象中。然后把对象做为一个数据给设置到当前线程中 * 现在我们来实现一下: * 我们创建一个MyThreadLocalScopeData然后创建两个变量一个为NAME,一个为AGE * 我们实现它的SET,GET方法。然后来操作 * 效果做出来了但是并不好。程序很不好 Thread-1 has put data :-779569351 Thread-0 has put data :443820472 ModuleA from:Thread-0 get data :443820472 ModuleA from:Thread-0 get datas :name:443820472,443820472 ModuleA from:Thread-1 get data :-779569351 ModuleA from:Thread-1 get datas :name:-779569351,-779569351 ModuleB from:Thread-0 get data :443820472 ModuleB from:Thread-0 get datas :name:443820472,443820472 ModuleB from:Thread-1 get data :-779569351 ModuleB from:Thread-1 get datas :name:-779569351,-779569351 为什么说程序做的并不好了? 我们每new一个实例。线程中就会多出一个这样的实例。如果很多,那线程 中这个对象的实例就不是一个了,我们在模块中获取这个线程中的实例的时候就会出现问题 因为这里根本就没有体现出ThreadLocal,我们来对这个代码进行改写 */ // private static int data = 0; // 我们要实现范围内的数据共享 // 即线程上的所有的模块使用的对象都是同一个 // 我们可以使用map集合来实现,把线程和数据给封装成EntrySet对象。填充到 map集合中 // private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>(); //使用ThreadLocal改写 private static ThreadLocal<Integer> x=new ThreadLocal<Integer>(); private static ThreadLocal<ThreadScopeData> myThreadData=new ThreadLocal<ThreadScopeData>(); public static void main(String[] args) { for (int i = 0; i < 2; i++) { new Thread(new Runnable() { public void run() { // 给设置赋值 int data = new Random().nextInt(); System.out.println(Thread.currentThread().getName() + " has put data :" + data); // 把当前的线程和要做为在当前线程内共享的数据添加到map集合中 //把当前的线程做为key,把共享的数据做为value添加到map集合中 // threadData.put(Thread.currentThread(), data); x.set(data); //我们是直接把做为线程上共享的数据设置上当前线程上去的。 //创建MyThreadLocalScopeData对象的实例 /*MyThreadLocalScopeData myData=new MyThreadLocalScopeData(); * //创建了一个对象 //我们每new一个实例。线程中就会多出一个这样的实例。如果很多,那线程 中这个对象的实例就不是一个了,我们在模块中获取这个线程中的实例的时候就会出现问题 myData.setName("name:"+data); myData.setAge(data); //把对象给设置到myThreadData中 myThreadData.set(myData);*/ //获取本线程相关的实例对象。然后给本线程相关的实例对象设置数据 ThreadScopeData.getInstance().setName("name"+data); ThreadScopeData.getInstance().setAge(data); //这和我们上面的操作是不一样的。 new ModuleA().getData(); new ModuleB().getData(); } }).start(); } } static class ModuleA { public void getData() { int data=x.get(); System.out.println("ModuleA from:" + Thread.currentThread().getName() + " get data :" + data); //我们来取数据,如果上面的代码中我们new的类的实例有很多个,数据也有很多种。 //我们在这里就会出现问题 // MyThreadLocalScopeData myData=myThreadData.get(); ThreadScopeData myData=ThreadScopeData.getInstance(); System.out.println("ModuleA from:" + Thread.currentThread().getName() + " get datas :" + myData.getName()+ ","+myData.getAge()); } } static class ModuleB { public void getData() { int data=x.get(); System.out.println("ModuleB from:" + Thread.currentThread().getName() + " get data :" + data); //我们来取数据 // MyThreadLocalScopeData myData=myThreadData.get(); //获取线程上绑定的对象 ThreadScopeData myData=ThreadScopeData.getInstance(); //获取线程上绑定的对象 System.out.println("ModuleB from:" + Thread.currentThread().getName() + " get datas :" + myData.getName()+","+ myData.getAge()); } } } //当数据多的时候我们使用对象进行封装 /** * 总结: * 我们这里其实是把封装多个数据的类做成了单例,然后在这个类的内部封装了一个ThreadLocal, * 在获取本类实例的静态方法中,首先是从当前线程获取是否有本类的实例存在,如果存在就返回, * 如果没有存在,就创建,然后把创建的本类的实例设置到当前的线程中。然后返回这个对象。使用 * 这个样的封装。避免了保证了封装数据的对象在线程中的唯一性。保证了每个模块是从同一个对象 * 身上获取的数据。 * */ class ThreadScopeData{ //我们先把这个类给做成单例的 private ThreadScopeData(){} //别人不能创建这个类的实例对象但是可以通过我们提供的静态方法来获取这个类的实例对象 public static ThreadScopeData getInstance(){ //饥汉模式存在一种线程安全的问题。就是说A线程调用了这个语句,要获取这个类的实例,但是 //这个类的实例不存在,所以它就需要创建,还没有给这个引用赋值,这个时候B线程也进来了。 //它判断对象也没有所以它也创建了。所以在内存里面就出现了两个这个类的实例。 //这个时候了。我们就需要把这个方法做成互斥的。 // return instance; //饱汉模式是直接返回创建好的实例对象 //我们在调用这个方法的时候就直接去当前线程上取数据 //取到的是于当前线程相关的实例对象 ThreadScopeData instance=myThreadData.get(); //获取与本线程相关的实例对象 //我们把当前这个类的实例对象,因为这个类是单例的所以只能有一个实例对象,我们把它给添加到 //到当前线程上去了。就意味着在当前线程中只会有一个实例对象的存在。这样在当前线程上的所有模块 //拿到的数据都是同一份,因为我们的这个方法最开始的语句是从当前线程中获取实例对象,如果对象 //存在就返回对象如果不存在就创建对象然后设置到线程中。A线程在没有获取到实例对象创建的时候, //还没有赋值。B线程也没有获取到。它也创建。这个时候我们的方法是可以不用互斥的。因为它们是从不 //同的线程中获取的实例对象。A线程不会影响到B线程。B线程也不会影响到A线程。 if(instance==null){ //在调用这个方法的时候,如果对象存在就直接返回 //如果对象不存在就创建这个对象然后返回这个对象的实例 instance=new ThreadScopeData(); //如果对象存在就直接返回如果不存在就创建这个对象给设置到当前线程上去。 myThreadData.set(instance); } return instance; } //我们这么来做,我们把当前创建的对象的实例给设置到当前的线程中 private static ThreadLocal<ThreadScopeData> myThreadData=new ThreadLocal<ThreadScopeData>(); //顺便演示一下单例中的两种模式 //这是饱汉模式:对象直接创建创建好的。它是随着类的加载而加载 // private static MyThreadLocalScopeData instance=new MyThreadLocalScopeData(); //这里是饥汉模式:对象引用先创建但是赋的是空值 // private static MyThreadLocalScopeData instance=null; 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中存多数据时的分析,布布扣,bubuko.com
原文:http://blog.csdn.net/zhizguoz/article/details/22577673