`

对TheadLocal的理解和使用

阅读更多

这两天对ThreadLocal了解了下,通过google,很多文章都多是同一个说法,ThreadLocal为每个使用该变量的线程提供独立的变量副本,刚开始的时候就是这样理解的:假如说线程A和线程B共享变量c,那么通过ThreadLocal呢,我们就可以通过使用ThreaLocal这个类来使线程A和线程B各自拥有贡献变量c的副本,这样就不用锁了,就可以线程安全了,嘿嘿 ,性能和安全双收。恩,觉得这思想so good,那我就来写个小例子吧。

   代码如下:

   先是共享变量c的代码:

public class Student {
	int i;
	String name;
	
	public Student(int i, String name) {
		super();
		this.i = i;
		this.name = name;
		seqNum.set(this);
	}
	
		
	@Override
	public String toString() {
		return "name:" + name + "  age:" + i;
	}

  接着test类

public class ThreadLocalTest {

	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		
		//new 一个线程共享的student对象
		Student s = new Student(8, "张三");
		//线程one
		new Thread(new ThreadT(s), "one").start();
		//线程two
		new Thread(new ThreadT(s), "two").start();
		//打印共享对象的信息
		p("smain:       " + s);
		//主线程睡眠一下,保证线程one和线程two已经运行完
		Thread.sleep(1000);
		//再次打印共享对象的信息
		p("main:        " + s);
	}

	private static void p(Object o) {
		System.out.println(o);
	}
	
	
	private static class ThreadT implements Runnable {
		private Student student;
		private ThreadLocal<Student> studentThreadLocal = new ThreadLocal<Student>();
		
		public ThreadT(Student s) {
			super();
			this.student = s;
			//将s放发threadLocal中
			studentThreadLocal.set(s);
		}

		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			
			//从ThreadLocal中取得所说的共享对象student副本(注意这是错误的想法的示例)
			Student s = studentThreadLocal.get();
			
			if(null == s) {
				p(threadName + ":         get()为null执行set()方法");
//				p(student);
				studentThreadLocal.set(this.student);
				//再次从ThreadLocal中获取
				s =  studentThreadLocal.get();
			}
			
			//改变贡献对象副本的对象的属性(这也是错误想法的示例)
			s.name= "李四";
//			studentThreadLocal.set(s);
			p(threadName + ":         " + studentThreadLocal.get());
			}
}

 结果运行结果是

two:         get()为null执行set()方法
two:         name:李四  age:8
one:         get()为null执行set()方法
smain:       name:张三  age:8
one:         name:李四  age:8
main:        name:李四  age:8

   可以看到,在线程中的改变对会反映到共享变量中,不是说会帮我们生产副本吗?

于是我就去大概看看了ThreadLocal的源码,发现ThreadLocal的get方法是从ThreadLocalMap中去取,存呢是将当前线程为key,value为我们调用方法set的参数,让后我着重看了map的set方法,他是直接将我们向set方法所传的对象的引用放到map里的,并没有什么副本啊。难道是我那里弄错了?

    然后我去看看别个的例子,发现别个和我写的例子蛮大的区别的。于是乎我就把我的例子改了下。

 student如下:

public class Student {
	int i;
	String name;
	
	public Student(int i, String name) {
		super();
		this.i = i;
		this.name = name;
		studentThreadLocal.set(this);
	}
	
		
	@Override
	public String toString() {
		return "name:" + name + "  age:" + i;
	}
	
	public static ThreadLocal<Student> studentThreadLocal = new ThreadLocal<Student>()	;
	
	
}

 和之前没有什么区别,就是多了个ThreadLocal静态变量

在看测试类

public class ThreadLocalTest {

	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		
		//new 一个线程共享的student对象
		Student s = new Student(8, "张三");
		//线程one
		new Thread(new ThreadT(s), "one").start();
		//线程two
		new Thread(new ThreadT(s), "two").start();
		//打印共享对象的信息
		p("smain:       " + s);
		//主线程睡眠一下,保证线程one和线程two已经运行完
		Thread.sleep(1000);
		//再次打印共享对象的信息
		p("main:        " + s);
	}

	private static void p(Object o) {
		System.out.println(o);
	}
	
	
	private static class ThreadT implements Runnable {
		private Student student;
		
//		private ThreadLocal<Student> studentThreadLocal = new ThreadLocal<Student>();
		
		public ThreadT(Student s) {
			super();
			this.student = s;
			//将s放发threadLocal中
//			studentThreadLocal.set(s);
		}

		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			
			//从ThreadLocal中取得所说的共享对象student副本(注意这是错误的想法的示例)
//			Student s = studentThreadLocal.get();
			
/*			if(null == s) {
				p(threadName + ":         get()为null执行set()方法");
				studentThreadLocal.set(this.student);
				//再次从ThreadLocal中获取
				s =  studentThreadLocal.get();
			}*/
			
			//改变贡献对象副本的对象的属性
//			s.name= "李四";
			
			p(threadName + ":         " + getStudent());
			

			/*	System.out.println("thread["+threadName +

				"] sn["+s.getNextNum()+"]");*/
		
		}
	
		public Student getStudent(){
			
			Student s = Student.studentThreadLocal.get();
			if(null == s) {
				if(Thread.currentThread().getName().equals("one")) {
					Student.studentThreadLocal.set(new Student(9,"李四"));
				}else {
					Student.studentThreadLocal.set(new Student(10,"王五"));
				}
			}

			return Student.studentThreadLocal.get();

		}
	}

   这次run方法就是调用了getStudent方法。直接去取,取不到就new个放进去,

这次的运行结果

two:         name:王五  age:10
one:         name:李四  age:9
smain:       name:张三  age:8
main:        name:张三  age:8

 这次好像很正常,mian方法里的输出是没有什么变化。

可是仔细看看代码,发现在线程中压根都没有用到传递进去的student, 没取到是放进去的是new出来的一个

,好,我们不new,直接将传递进去的student放进去,在取出来,再改变值后再放进去,

   对getStudent做如下改变:

public Student getStudent(){
			
			Student s = Student.studentThreadLocal.get();
			if(null == s) {
				if(Thread.currentThread().getName().equals("one")) {
					Student.studentThreadLocal.set(new Student(9,"李四"));
				}else {
//					Student.studentThreadLocal.set(new Student(10,"王五"));
					//将共享变量放入
					Student.studentThreadLocal.set(this.student);
					//再次将它取出去
					s = Student.studentThreadLocal.get();
					//改变值
					s.i = 10;
					s.name = "王五";
				}
			}
			
			return Student.studentThreadLocal.get();

		}

 这次输出结果:

one:         name:李四  age:9
smain:       name:张三  age:8
two:         name:王五  age:10
main:        name:王五  age:10

 这回又main方法的边了。

 副本呢,副本在那!

这下就郁闷了,边看源码和网上的文章,从源码来看,

 

先看ThreadLocal的get方法代码

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

 get方法会先得到当前的线程,然后再从当前的线程中获得ThreadLocalMap(这个ThreadLocalMap有点奇怪哦,它是在ThreadLocal中定义的内部类,却是Thread类的一个成员变量

然后在map中以调用get方法的ThreadLocal的threadLocalHashCode(不明白怎么意思)和map里面的table.lenth来获取下面是获取的代码:

 

 

    private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

set方法和get方法类似。就不说了

在从例子方面说,

  每次都是先去取,取不到的时候就new一个让后在放进去。这个和共享变量好像根本没什么关系,

这东西感觉和web的session很像,一个个线程就像是一个个会话,一个会话共享一个session,而一个线程通过一个ThreadLocal的set方法放个对象的引用进去,那么在这个线程的任何时刻都可以同过这个ThreadLocal去得到set进去的对象的引用。

结论:ThreadLocal并没有解决共享变量的问题,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,是在自己线程中产生的,其他线程是不需要访问的,也访问不到的。只不过,通过ThreadLocal的set的对象,在这个线程的任意时刻都是可以通过set的那个ThreadLocal的get获得set进去的对象的引用,和web的session有点类似。特别的,要是set进去的对象是共享变量,那还是会有并发的错误。

 

使用的情况:对每一个线程都必须持有一个类的实例,而且这个类是可变的(不可变的就是线程安全的,全部线程使用一个就可以了),例如hibernate对session的处理。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics